Merge branch 'github-dev' into feat/ui-1.1.0
# Conflicts: # i18n/zh_CN.yaml
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
FROM amd64/node AS node-builder
|
||||
|
||||
LABEL maintainer="mingcheng<mc@sf.com>"
|
||||
|
||||
COPY . /answer
|
||||
WORKDIR /answer
|
||||
RUN make install-ui-packages ui && mv ui/build /tmp
|
||||
|
||||
# stage2 build the main binary within static resource
|
||||
FROM golang:1.19-alpine AS golang-builder
|
||||
LABEL maintainer="aichy@sf.com"
|
||||
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV GOROOT /usr/local/go
|
||||
ENV PACKAGE github.com/answerdev/answer
|
||||
ENV BUILD_DIR ${GOPATH}/src/${PACKAGE}
|
||||
|
||||
ARG TAGS="sqlite sqlite_unlock_notify"
|
||||
ENV TAGS "bindata timetzdata $TAGS"
|
||||
ARG CGO_EXTRA_CFLAGS
|
||||
|
||||
COPY . ${BUILD_DIR}
|
||||
WORKDIR ${BUILD_DIR}
|
||||
COPY --from=node-builder /tmp/build ${BUILD_DIR}/ui/build
|
||||
RUN apk --no-cache add build-base git \
|
||||
&& make clean build \
|
||||
&& cp answer /usr/bin/answer
|
||||
|
||||
RUN mkdir -p /data/uploads && chmod 777 /data/uploads \
|
||||
&& mkdir -p /data/i18n && cp -r i18n/*.yaml /data/i18n
|
||||
|
||||
# stage3 copy the binary and resource files into fresh container
|
||||
FROM alpine
|
||||
LABEL maintainer="maintainers@sf.com"
|
||||
|
||||
ENV TZ "Asia/Shanghai"
|
||||
RUN apk update \
|
||||
&& apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
dumb-init \
|
||||
gettext \
|
||||
openssh \
|
||||
sqlite \
|
||||
gnupg \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone
|
||||
|
||||
COPY --from=golang-builder /usr/bin/answer /usr/bin/answer
|
||||
COPY --from=golang-builder /data /data
|
||||
COPY /script/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod 755 /entrypoint.sh
|
||||
|
||||
VOLUME /data
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -1,8 +1,8 @@
|
||||
name: Build Docker Hub Image
|
||||
name: Build DockerHub Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: [ "main","githubaction","test" ]
|
||||
tags:
|
||||
- v2.*
|
||||
- v1.*
|
||||
@@ -12,49 +12,48 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: [self-hosted, linux]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: answerdev/answer
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,enable=true,priority=600,prefix=,suffix=,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: answerdev/answer
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
# branch event
|
||||
type=ref,enable=true,priority=600,prefix=,suffix=,event=branch
|
||||
# tag event
|
||||
#type=ref,enable=true,priority=600,prefix=,suffix=,event=tag
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
|
||||
# - name: Login to GitHub Container Registry
|
||||
# uses: docker/login-action@v2
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: ./.github/Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
name: Build GitHub Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
tags:
|
||||
- 2.*
|
||||
- 1.*
|
||||
- 0.*
|
||||
# pull_request:
|
||||
# branches: [ "main" ]
|
||||
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE: answerdev/answer
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
|
||||
|
||||
- name: Build Img
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
# - name: build to hub.docker
|
||||
# run: |
|
||||
# docker build -t answerdev/answer -f ./Dockerfile .
|
||||
# - name: Login to hub.docker Registry
|
||||
# run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }}
|
||||
# - name: Push Image to hub.docker
|
||||
# run: |
|
||||
# docker push answerdev/answer
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: "Goreleaser"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-goreleaser:
|
||||
runs-on: [self-hosted, linux]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Node Build
|
||||
run: make install-ui-packages ui
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: answer
|
||||
path: ./dist/*
|
||||
@@ -2,14 +2,14 @@ name: Go Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: [ "main","githubaction","test" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
go-build-test:
|
||||
runs-on: [self-hosted, linux]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -2,14 +2,13 @@ name: Node Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: [ "main","githubaction","test"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
node-build-test:
|
||||
runs-on: [self-hosted, linux]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
name: Build PR Image
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
|
||||
jobs:
|
||||
build-answer:
|
||||
name: Build and push `Answer`
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
if: ${{ github.event.action != 'closed' }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate UUID image name
|
||||
id: uuid
|
||||
run: echo "UUID_WORKER=$(uuidgen)" >> $GITHUB_ENV
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: registry.uffizzi.com/${{ env.UUID_WORKER }}
|
||||
tags: |
|
||||
type=raw,value=60d
|
||||
|
||||
- name: Build and Push Image to registry.uffizzi.com - Uffizzi's ephemeral Registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha, mode=max
|
||||
|
||||
render-compose-file:
|
||||
name: Render Docker Compose File
|
||||
# Pass output of this workflow to another triggered by `workflow_run` event.
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-answer
|
||||
outputs:
|
||||
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Render Compose File
|
||||
run: |
|
||||
ANSWER_IMAGE=${{ needs.build-answer.outputs.tags }}
|
||||
export ANSWER_IMAGE
|
||||
export UFFIZZI_URL=\$UFFIZZI_URL
|
||||
# Render simple template from environment variables.
|
||||
envsubst < docker-compose.uffizzi.yml > docker-compose.rendered.yml
|
||||
cat docker-compose.rendered.yml
|
||||
- name: Upload Rendered Compose File as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: docker-compose.rendered.yml
|
||||
retention-days: 2
|
||||
- name: Serialize PR Event to File
|
||||
run: |
|
||||
cat << EOF > event.json
|
||||
${{ toJSON(github.event) }}
|
||||
|
||||
EOF
|
||||
- name: Upload PR Event as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: event.json
|
||||
retention-days: 2
|
||||
|
||||
delete-preview:
|
||||
name: Call for Preview Deletion
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action == 'closed' }}
|
||||
steps:
|
||||
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
|
||||
- name: Serialize PR Event to File
|
||||
run: |
|
||||
cat << EOF > event.json
|
||||
${{ toJSON(github.event) }}
|
||||
|
||||
EOF
|
||||
- name: Upload PR Event as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: event.json
|
||||
retention-days: 2
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Deploy Uffizzi Preview
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- "Build PR Image"
|
||||
types:
|
||||
- completed
|
||||
|
||||
|
||||
jobs:
|
||||
cache-compose-file:
|
||||
name: Cache Compose File
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
outputs:
|
||||
compose-file-cache-key: ${{ env.HASH }}
|
||||
pr-number: ${{ env.PR_NUMBER }}
|
||||
steps:
|
||||
- name: 'Download artifacts'
|
||||
# Fetch output (zip archive) from the workflow run that triggered this workflow.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.payload.workflow_run.id,
|
||||
});
|
||||
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "preview-spec"
|
||||
})[0];
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));
|
||||
|
||||
- name: 'Unzip artifact'
|
||||
run: unzip preview-spec.zip
|
||||
- name: Read Event into ENV
|
||||
run: |
|
||||
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
|
||||
cat event.json >> $GITHUB_ENV
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
|
||||
- name: Hash Rendered Compose File
|
||||
id: hash
|
||||
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
|
||||
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
|
||||
run: echo "HASH=$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV
|
||||
- name: Cache Rendered Compose File
|
||||
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: docker-compose.rendered.yml
|
||||
key: ${{ env.HASH }}
|
||||
|
||||
- name: Read PR Number From Event Object
|
||||
id: pr
|
||||
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
|
||||
- name: DEBUG - Print Job Outputs
|
||||
if: ${{ runner.debug }}
|
||||
run: |
|
||||
echo "PR number: ${{ env.PR_NUMBER }}"
|
||||
echo "Compose file hash: ${{ env.HASH }}"
|
||||
cat event.json
|
||||
|
||||
deploy-uffizzi-preview:
|
||||
name: Use Remote Workflow to Preview on Uffizzi
|
||||
needs:
|
||||
- cache-compose-file
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2
|
||||
with:
|
||||
# If this workflow was triggered by a PR close event, cache-key will be an empty string
|
||||
# and this reusable workflow will delete the preview deployment.
|
||||
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
|
||||
compose-file-cache-path: docker-compose.rendered.yml
|
||||
server: https://app.uffizzi.com
|
||||
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
+4
-1
@@ -15,8 +15,11 @@
|
||||
/configs/config-dev.yaml
|
||||
/go.work*
|
||||
/logs
|
||||
/ui/build
|
||||
/ui/node_modules
|
||||
/ui/build/*/*/*
|
||||
/ui/build/*.json
|
||||
/ui/build/*.html
|
||||
/ui/build/*.txt
|
||||
/vendor
|
||||
Thumbs*.db
|
||||
tmp
|
||||
|
||||
+8
-40
@@ -2,7 +2,7 @@ env:
|
||||
- GO11MODULE=on
|
||||
- GO111MODULE=on
|
||||
- GOPROXY=https://goproxy.io
|
||||
- CGO_ENABLED=1
|
||||
- CGO_ENABLED=0
|
||||
|
||||
before:
|
||||
hooks:
|
||||
@@ -16,74 +16,48 @@ builds:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
# linux windows need cgomingw64-gcc
|
||||
- id: build-windows
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
env:
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
- CXX=x86_64-w64-mingw32-g++
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
# linux arm64 need cgo arm64
|
||||
- id: build-arm64
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
env:
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
- CXX=aarch64-linux-gnu-g++
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
- id: build-arm7
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
env:
|
||||
- CC=arm-linux-gnueabihf-gcc
|
||||
- CXX=arm-linux-gnueabihf-g++
|
||||
- AR=arm-linux-gnueabihf-ar
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm
|
||||
goarm:
|
||||
- 7
|
||||
- arm64
|
||||
- id: build-darwin-arm64
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
env:
|
||||
- CC=oa64-clang
|
||||
- CXX=oa64-clang++
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
flags: -v
|
||||
- id: build-darwin-amd64
|
||||
main: ./cmd/answer/.
|
||||
binary: answer
|
||||
env:
|
||||
- CC=o64-clang
|
||||
- CXX=o64-clang++
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
ldflags: -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} -X main.Time={{.Date}} -X main.BuildUser=goreleaser
|
||||
flags: -v
|
||||
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: Darwin
|
||||
darwin: macOS
|
||||
amd64: x86_64
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
@@ -95,10 +69,4 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
|
||||
# sudo apt-get install build-essential
|
||||
# sudo apt-get install gcc-multilib g++-multilib
|
||||
# sudo apt-get install gcc-mingw-w64
|
||||
# sudo apt-get -y install gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf
|
||||
# sudo apt-get install clang llvm
|
||||
# goreleaser release --snapshot --rm-dist
|
||||
# goreleaser release --snapshot --rm-dist
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
.PHONY: build clean ui
|
||||
|
||||
VERSION=1.0.3
|
||||
VERSION=1.0.4
|
||||
BIN=answer
|
||||
DIR_SRC=./cmd/answer
|
||||
DOCKER_CMD=docker
|
||||
|
||||
#GO_ENV=CGO_ENABLED=0
|
||||
GO_ENV=CGO_ENABLED=0 GO111MODULE=on
|
||||
Revision=$(shell git rev-parse --short HEAD)
|
||||
GO_FLAGS=-ldflags="-X main.Version=$(VERSION) -X 'main.Revision=$(Revision)' -X 'main.Time=`date`' -extldflags -static"
|
||||
GO=$(GO_ENV) $(shell which go)
|
||||
|
||||
build:
|
||||
@$(GO_ENV) $(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
|
||||
build: generate
|
||||
@$(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
|
||||
|
||||
# https://dev.to/thewraven/universal-macos-binaries-with-go-1-16-3mm3
|
||||
universal:
|
||||
universal: generate
|
||||
@GOOS=darwin GOARCH=amd64 $(GO_ENV) $(GO) build $(GO_FLAGS) -o ${BIN}_amd64 $(DIR_SRC)
|
||||
@GOOS=darwin GOARCH=arm64 $(GO_ENV) $(GO) build $(GO_FLAGS) -o ${BIN}_arm64 $(DIR_SRC)
|
||||
@lipo -create -output ${BIN} ${BIN}_amd64 ${BIN}_arm64
|
||||
@rm -f ${BIN}_amd64 ${BIN}_arm64
|
||||
|
||||
generate:
|
||||
go get github.com/google/wire/cmd/wire@latest
|
||||
go install github.com/golang/mock/mockgen@v1.6.0
|
||||
go generate ./...
|
||||
go mod tidy
|
||||
@$(GO) get github.com/google/wire/cmd/wire@v0.5.0
|
||||
@$(GO) get github.com/golang/mock/mockgen@v1.6.0
|
||||
@$(GO) install github.com/google/wire/cmd/wire@v0.5.0
|
||||
@$(GO) install github.com/golang/mock/mockgen@v1.6.0
|
||||
@$(GO) generate ./...
|
||||
@$(GO) mod tidy
|
||||
|
||||
test:
|
||||
@$(GO) test ./internal/repo/repo_test
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `security@answer.dev`
|
||||
@@ -0,0 +1,36 @@
|
||||
version: "3"
|
||||
|
||||
# uffizzi integration
|
||||
x-uffizzi:
|
||||
ingress:
|
||||
service: answer
|
||||
port: 80
|
||||
|
||||
services:
|
||||
|
||||
answer:
|
||||
image: "${ANSWER_IMAGE}"
|
||||
volumes:
|
||||
- answer-data:/data
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4000M
|
||||
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
environment:
|
||||
- MYSQL_DATABASE=answer
|
||||
- MYSQL_ROOT_PASSWORD=password
|
||||
- MYSQL_USER=mysql
|
||||
- MYSQL_PASSWORD=mysql
|
||||
volumes:
|
||||
- sql_data:/var/lib/mysql
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 500M
|
||||
|
||||
volumes:
|
||||
answer-data:
|
||||
sql_data:
|
||||
+71
-57
@@ -2911,6 +2911,45 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/post/render": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "render post content",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Upload"
|
||||
],
|
||||
"summary": "render post content",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "PostRenderReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.PostRenderReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -5425,8 +5464,7 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"object_id",
|
||||
"original_text",
|
||||
"parsed_text"
|
||||
"original_text"
|
||||
],
|
||||
"properties": {
|
||||
"mention_username_list": {
|
||||
@@ -5442,11 +5480,9 @@ const docTemplate = `{
|
||||
},
|
||||
"original_text": {
|
||||
"description": "original comment content",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed comment content",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 600,
|
||||
"minLength": 2
|
||||
},
|
||||
"reply_comment_id": {
|
||||
"description": "reply comment id",
|
||||
@@ -5535,46 +5571,41 @@ const docTemplate = `{
|
||||
},
|
||||
"schema.AnswerAddReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "content",
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"question_id": {
|
||||
"description": "question_id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerUpdateReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "content",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"edit_summary": {
|
||||
"description": "edit_summary",
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "id",
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"description": "question_id",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "title",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -6589,11 +6620,18 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.PostRenderReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.QuestionAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"html",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
@@ -6604,12 +6642,6 @@ const docTemplate = `{
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"tags": {
|
||||
"description": "tags",
|
||||
"type": "array",
|
||||
@@ -6669,6 +6701,9 @@ const docTemplate = `{
|
||||
"collection_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6739,7 +6774,6 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"html",
|
||||
"id",
|
||||
"tags",
|
||||
"title"
|
||||
@@ -6755,12 +6789,6 @@ const docTemplate = `{
|
||||
"description": "edit summary",
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"id": {
|
||||
"description": "question id",
|
||||
"type": "string"
|
||||
@@ -7326,10 +7354,6 @@ const docTemplate = `{
|
||||
"description": "original text",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed text",
|
||||
"type": "string"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug_name",
|
||||
"type": "string",
|
||||
@@ -7416,7 +7440,8 @@ const docTemplate = `{
|
||||
"schema.UpdateCommentReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment_id"
|
||||
"comment_id",
|
||||
"original_text"
|
||||
],
|
||||
"properties": {
|
||||
"comment_id": {
|
||||
@@ -7425,11 +7450,9 @@ const docTemplate = `{
|
||||
},
|
||||
"original_text": {
|
||||
"description": "original comment content",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed comment content",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 600,
|
||||
"minLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7460,11 +7483,6 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"maxLength": 4096
|
||||
},
|
||||
"bio_html": {
|
||||
"description": "bio",
|
||||
"type": "string",
|
||||
"maxLength": 4096
|
||||
},
|
||||
"display_name": {
|
||||
"description": "display_name",
|
||||
"type": "string",
|
||||
@@ -7549,10 +7567,6 @@ const docTemplate = `{
|
||||
"description": "original text",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed text",
|
||||
"type": "string"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug_name",
|
||||
"type": "string",
|
||||
|
||||
+71
-57
@@ -2899,6 +2899,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/post/render": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "render post content",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Upload"
|
||||
],
|
||||
"summary": "render post content",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "PostRenderReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.PostRenderReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question": {
|
||||
"put": {
|
||||
"security": [
|
||||
@@ -5413,8 +5452,7 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"object_id",
|
||||
"original_text",
|
||||
"parsed_text"
|
||||
"original_text"
|
||||
],
|
||||
"properties": {
|
||||
"mention_username_list": {
|
||||
@@ -5430,11 +5468,9 @@
|
||||
},
|
||||
"original_text": {
|
||||
"description": "original comment content",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed comment content",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 600,
|
||||
"minLength": 2
|
||||
},
|
||||
"reply_comment_id": {
|
||||
"description": "reply comment id",
|
||||
@@ -5523,46 +5559,41 @@
|
||||
},
|
||||
"schema.AnswerAddReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "content",
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"question_id": {
|
||||
"description": "question_id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.AnswerUpdateReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "content",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"edit_summary": {
|
||||
"description": "edit_summary",
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "id",
|
||||
"type": "string"
|
||||
},
|
||||
"question_id": {
|
||||
"description": "question_id",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "title",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -6577,11 +6608,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.PostRenderReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.QuestionAdd": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"html",
|
||||
"tags",
|
||||
"title"
|
||||
],
|
||||
@@ -6592,12 +6630,6 @@
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"tags": {
|
||||
"description": "tags",
|
||||
"type": "array",
|
||||
@@ -6657,6 +6689,9 @@
|
||||
"collection_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6727,7 +6762,6 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"html",
|
||||
"id",
|
||||
"tags",
|
||||
"title"
|
||||
@@ -6743,12 +6777,6 @@
|
||||
"description": "edit summary",
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"description": "html",
|
||||
"type": "string",
|
||||
"maxLength": 65535,
|
||||
"minLength": 6
|
||||
},
|
||||
"id": {
|
||||
"description": "question id",
|
||||
"type": "string"
|
||||
@@ -7314,10 +7342,6 @@
|
||||
"description": "original text",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed text",
|
||||
"type": "string"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug_name",
|
||||
"type": "string",
|
||||
@@ -7404,7 +7428,8 @@
|
||||
"schema.UpdateCommentReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment_id"
|
||||
"comment_id",
|
||||
"original_text"
|
||||
],
|
||||
"properties": {
|
||||
"comment_id": {
|
||||
@@ -7413,11 +7438,9 @@
|
||||
},
|
||||
"original_text": {
|
||||
"description": "original comment content",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed comment content",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 600,
|
||||
"minLength": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7448,11 +7471,6 @@
|
||||
"type": "string",
|
||||
"maxLength": 4096
|
||||
},
|
||||
"bio_html": {
|
||||
"description": "bio",
|
||||
"type": "string",
|
||||
"maxLength": 4096
|
||||
},
|
||||
"display_name": {
|
||||
"description": "display_name",
|
||||
"type": "string",
|
||||
@@ -7537,10 +7555,6 @@
|
||||
"description": "original text",
|
||||
"type": "string"
|
||||
},
|
||||
"parsed_text": {
|
||||
"description": "parsed text",
|
||||
"type": "string"
|
||||
},
|
||||
"slug_name": {
|
||||
"description": "slug_name",
|
||||
"type": "string",
|
||||
|
||||
+44
-42
@@ -147,9 +147,8 @@ definitions:
|
||||
type: string
|
||||
original_text:
|
||||
description: original comment content
|
||||
type: string
|
||||
parsed_text:
|
||||
description: parsed comment content
|
||||
maxLength: 600
|
||||
minLength: 2
|
||||
type: string
|
||||
reply_comment_id:
|
||||
description: reply comment id
|
||||
@@ -157,7 +156,6 @@ definitions:
|
||||
required:
|
||||
- object_id
|
||||
- original_text
|
||||
- parsed_text
|
||||
type: object
|
||||
schema.AddReportReq:
|
||||
properties:
|
||||
@@ -217,35 +215,30 @@ definitions:
|
||||
schema.AnswerAddReq:
|
||||
properties:
|
||||
content:
|
||||
description: content
|
||||
type: string
|
||||
html:
|
||||
description: html
|
||||
maxLength: 65535
|
||||
minLength: 6
|
||||
type: string
|
||||
question_id:
|
||||
description: question_id
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
type: object
|
||||
schema.AnswerUpdateReq:
|
||||
properties:
|
||||
content:
|
||||
description: content
|
||||
maxLength: 65535
|
||||
minLength: 6
|
||||
type: string
|
||||
edit_summary:
|
||||
description: edit_summary
|
||||
type: string
|
||||
html:
|
||||
description: html
|
||||
type: string
|
||||
id:
|
||||
description: id
|
||||
type: string
|
||||
question_id:
|
||||
description: question_id
|
||||
type: string
|
||||
title:
|
||||
description: title
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
type: object
|
||||
schema.AvatarInfo:
|
||||
properties:
|
||||
@@ -976,6 +969,11 @@ definitions:
|
||||
type:
|
||||
type: string
|
||||
type: object
|
||||
schema.PostRenderReq:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
type: object
|
||||
schema.QuestionAdd:
|
||||
properties:
|
||||
content:
|
||||
@@ -983,11 +981,6 @@ definitions:
|
||||
maxLength: 65535
|
||||
minLength: 6
|
||||
type: string
|
||||
html:
|
||||
description: html
|
||||
maxLength: 65535
|
||||
minLength: 6
|
||||
type: string
|
||||
tags:
|
||||
description: tags
|
||||
items:
|
||||
@@ -1000,7 +993,6 @@ definitions:
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
- html
|
||||
- tags
|
||||
- title
|
||||
type: object
|
||||
@@ -1036,6 +1028,8 @@ definitions:
|
||||
type: integer
|
||||
collection_count:
|
||||
type: integer
|
||||
created_at:
|
||||
type: integer
|
||||
description:
|
||||
type: string
|
||||
follow_count:
|
||||
@@ -1090,11 +1084,6 @@ definitions:
|
||||
edit_summary:
|
||||
description: edit summary
|
||||
type: string
|
||||
html:
|
||||
description: html
|
||||
maxLength: 65535
|
||||
minLength: 6
|
||||
type: string
|
||||
id:
|
||||
description: question id
|
||||
type: string
|
||||
@@ -1110,7 +1099,6 @@ definitions:
|
||||
type: string
|
||||
required:
|
||||
- content
|
||||
- html
|
||||
- id
|
||||
- tags
|
||||
- title
|
||||
@@ -1490,9 +1478,6 @@ definitions:
|
||||
original_text:
|
||||
description: original text
|
||||
type: string
|
||||
parsed_text:
|
||||
description: parsed text
|
||||
type: string
|
||||
slug_name:
|
||||
description: slug_name
|
||||
maxLength: 35
|
||||
@@ -1558,12 +1543,12 @@ definitions:
|
||||
type: string
|
||||
original_text:
|
||||
description: original comment content
|
||||
type: string
|
||||
parsed_text:
|
||||
description: parsed comment content
|
||||
maxLength: 600
|
||||
minLength: 2
|
||||
type: string
|
||||
required:
|
||||
- comment_id
|
||||
- original_text
|
||||
type: object
|
||||
schema.UpdateFollowTagsReq:
|
||||
properties:
|
||||
@@ -1582,10 +1567,6 @@ definitions:
|
||||
description: bio
|
||||
maxLength: 4096
|
||||
type: string
|
||||
bio_html:
|
||||
description: bio
|
||||
maxLength: 4096
|
||||
type: string
|
||||
display_name:
|
||||
description: display_name
|
||||
maxLength: 30
|
||||
@@ -1648,9 +1629,6 @@ definitions:
|
||||
original_text:
|
||||
description: original text
|
||||
type: string
|
||||
parsed_text:
|
||||
description: parsed text
|
||||
type: string
|
||||
slug_name:
|
||||
description: slug_name
|
||||
maxLength: 35
|
||||
@@ -3684,6 +3662,30 @@ paths:
|
||||
summary: user's votes
|
||||
tags:
|
||||
- Activity
|
||||
/answer/api/v1/post/render:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: render post content
|
||||
parameters:
|
||||
- description: PostRenderReq
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.PostRenderReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: render post content
|
||||
tags:
|
||||
- Upload
|
||||
/answer/api/v1/question:
|
||||
delete:
|
||||
consumes:
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.18
|
||||
require (
|
||||
github.com/Chain-Zhang/pinyin v0.1.3
|
||||
github.com/anargu/gin-brotli v0.0.0-20220116052358-12bf532d5267
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
@@ -14,7 +15,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.11.1
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/wire v0.5.0
|
||||
github.com/gosimple/slug v1.13.1
|
||||
@@ -22,7 +23,6 @@ require (
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/microcosm-cc/bluemonday v1.0.21
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/ory/dockertest/v3 v3.9.1
|
||||
@@ -43,6 +43,7 @@ require (
|
||||
golang.org/x/net v0.1.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.14.2
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v1.3.2
|
||||
@@ -79,12 +80,14 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -97,6 +100,7 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
@@ -113,6 +117,7 @@ require (
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/image v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
@@ -120,5 +125,18 @@ require (
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.18 // indirect
|
||||
modernc.org/ccgo/v3 v3.12.82 // indirect
|
||||
modernc.org/libc v1.11.87 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.5 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
// github action runner Sometimes it will time out.
|
||||
replace gitee.com/travelliu/dm v1.8.11192 => github.com/aichy126/dm v1.8.11192
|
||||
replace modernc.org/z v1.2.19 => github.com/aichy126/modernc.org_z v1.2.19
|
||||
|
||||
@@ -38,7 +38,6 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -64,6 +63,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/aichy126/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -79,6 +79,8 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
@@ -142,6 +144,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
@@ -237,8 +240,9 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -696,6 +700,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -793,6 +798,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -832,6 +838,7 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@@ -912,9 +919,11 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1009,6 +1018,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
@@ -1219,7 +1229,9 @@ modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
|
||||
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||
@@ -1271,9 +1283,11 @@ modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
|
||||
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw=
|
||||
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao=
|
||||
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+122
-115
@@ -3,240 +3,247 @@
|
||||
backend:
|
||||
base:
|
||||
success:
|
||||
other: "Success."
|
||||
other: Success.
|
||||
unknown:
|
||||
other: "Unknown error."
|
||||
other: Unknown error.
|
||||
request_format_error:
|
||||
other: "Request format is not valid."
|
||||
other: Request format is not valid.
|
||||
unauthorized_error:
|
||||
other: "Unauthorized."
|
||||
other: Unauthorized.
|
||||
database_error:
|
||||
other: "Data server error."
|
||||
|
||||
other: Data server error.
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
other: "User"
|
||||
other: User
|
||||
admin:
|
||||
other: "Admin"
|
||||
other: Admin
|
||||
moderator:
|
||||
other: "Moderator"
|
||||
other: Moderator
|
||||
description:
|
||||
user:
|
||||
other: "Default with no special access."
|
||||
other: Default with no special access.
|
||||
admin:
|
||||
other: "Have the full power to access the site."
|
||||
other: Have the full power to access the site.
|
||||
moderator:
|
||||
other: "Has access to all posts except admin settings."
|
||||
|
||||
other: Has access to all posts except admin settings.
|
||||
email:
|
||||
other: "Email"
|
||||
other: Email
|
||||
password:
|
||||
other: "Password"
|
||||
|
||||
email_or_password_wrong_error: &email_or_password_wrong
|
||||
other: "Email and password do not match."
|
||||
|
||||
other: Password
|
||||
email_or_password_wrong_error:
|
||||
other: Email and password do not match.
|
||||
error:
|
||||
admin:
|
||||
email_or_password_wrong: *email_or_password_wrong
|
||||
email_or_password_wrong:
|
||||
other: Email and password do not match.
|
||||
answer:
|
||||
not_found:
|
||||
other: "Answer do not found."
|
||||
other: Answer do not found.
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: No permission to delete.
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: No permission to update.
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "Comment are not allowed to edit."
|
||||
other: Comment are not allowed to edit.
|
||||
not_found:
|
||||
other: "Comment not found."
|
||||
other: Comment not found.
|
||||
cannot_edit_after_deadline:
|
||||
other: The comment time has been too long to modify.
|
||||
email:
|
||||
duplicate:
|
||||
other: "Email already exists."
|
||||
other: Email already exists.
|
||||
need_to_be_verified:
|
||||
other: "Email should be verified."
|
||||
other: Email should be verified.
|
||||
verify_url_expired:
|
||||
other: "Email verified URL has expired, please resend the email."
|
||||
other: Email verified URL has expired, please resend the email.
|
||||
lang:
|
||||
not_found:
|
||||
other: "Language file not found."
|
||||
other: Language file not found.
|
||||
object:
|
||||
captcha_verification_failed:
|
||||
other: "Captcha wrong."
|
||||
other: Captcha wrong.
|
||||
disallow_follow:
|
||||
other: "You are not allowed to follow."
|
||||
other: You are not allowed to follow.
|
||||
disallow_vote:
|
||||
other: "You are not allowed to vote."
|
||||
other: You are not allowed to vote.
|
||||
disallow_vote_your_self:
|
||||
other: "You can't vote for your own post."
|
||||
other: You can't vote for your own post.
|
||||
not_found:
|
||||
other: "Object not found."
|
||||
other: Object not found.
|
||||
verification_failed:
|
||||
other: "Verification failed."
|
||||
other: Verification failed.
|
||||
email_or_password_incorrect:
|
||||
other: "Email and password do not match."
|
||||
other: Email and password do not match.
|
||||
old_password_verification_failed:
|
||||
other: "The old password verification failed"
|
||||
other: The old password verification failed
|
||||
new_password_same_as_previous_setting:
|
||||
other: "The new password is the same as the previous one."
|
||||
other: The new password is the same as the previous one.
|
||||
question:
|
||||
not_found:
|
||||
other: "Question not found."
|
||||
other: Question not found.
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: No permission to delete.
|
||||
cannot_close:
|
||||
other: "No permission to close."
|
||||
other: No permission to close.
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: No permission to update.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
other: Rank fail to meet the condition.
|
||||
report:
|
||||
handle_failed:
|
||||
other: "Report handle failed."
|
||||
other: Report handle failed.
|
||||
not_found:
|
||||
other: "Report not found."
|
||||
other: Report not found.
|
||||
tag:
|
||||
not_found:
|
||||
other: "Tag not found."
|
||||
other: Tag not found.
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
other: Recommend Tag is not exist.
|
||||
recommend_tag_enter:
|
||||
other: "Please enter at least one required tag."
|
||||
other: Please enter at least one required tag.
|
||||
not_contain_synonym_tags:
|
||||
other: "Should not contain synonym tags."
|
||||
other: Should not contain synonym tags.
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: No permission to update.
|
||||
cannot_set_synonym_as_itself:
|
||||
other: "You cannot set the synonym of the current tag as itself."
|
||||
other: You cannot set the synonym of the current tag as itself.
|
||||
smtp:
|
||||
config_from_name_cannot_be_email:
|
||||
other: "The From Name cannot be a email address."
|
||||
other: The From Name cannot be a email address.
|
||||
theme:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
other: Theme not found.
|
||||
revision:
|
||||
review_underway:
|
||||
other: "Can't edit currently, there is a version in the review queue."
|
||||
other: Can't edit currently, there is a version in the review queue.
|
||||
no_permission:
|
||||
other: "No permission to Revision."
|
||||
other: No permission to Revision.
|
||||
user:
|
||||
email_or_password_wrong:
|
||||
other: *email_or_password_wrong
|
||||
other:
|
||||
other: Email and password do not match.
|
||||
not_found:
|
||||
other: "User not found."
|
||||
other: User not found.
|
||||
suspended:
|
||||
other: "User has been suspended."
|
||||
other: User has been suspended.
|
||||
username_invalid:
|
||||
other: "Username is invalid."
|
||||
other: Username is invalid.
|
||||
username_duplicate:
|
||||
other: "Username is already in use."
|
||||
other: Username is already in use.
|
||||
set_avatar:
|
||||
other: "Avatar set failed."
|
||||
other: Avatar set failed.
|
||||
cannot_update_your_role:
|
||||
other: "You cannot modify your role."
|
||||
other: You cannot modify your role.
|
||||
not_allowed_registration:
|
||||
other: "Currently the site is not open for registration"
|
||||
other: Currently the site is not open for registration
|
||||
config:
|
||||
read_config_failed:
|
||||
other: "Read config failed"
|
||||
other: Read config failed
|
||||
database:
|
||||
connection_failed:
|
||||
other: "Database connection failed"
|
||||
other: Database connection failed
|
||||
create_table_failed:
|
||||
other: "Create table failed"
|
||||
other: Create table failed
|
||||
install:
|
||||
create_config_failed:
|
||||
other: "Can’t create the config.yaml file."
|
||||
other: Can't create the config.yaml file.
|
||||
upload:
|
||||
unsupported_file_format:
|
||||
other: Unsupported file format.
|
||||
report:
|
||||
spam:
|
||||
name:
|
||||
other: "spam"
|
||||
other: spam
|
||||
desc:
|
||||
other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
|
||||
other: This post is an advertisement, or vandalism. It is not useful or relevant
|
||||
to the current topic.
|
||||
rude:
|
||||
name:
|
||||
other: "rude or abusive"
|
||||
other: rude or abusive
|
||||
desc:
|
||||
other: "A reasonable person would find this content inappropriate for respectful discourse."
|
||||
other: A reasonable person would find this content inappropriate for respectful
|
||||
discourse.
|
||||
duplicate:
|
||||
name:
|
||||
other: "a duplicate"
|
||||
other: a duplicate
|
||||
desc:
|
||||
other: "This question has been asked before and already has an answer."
|
||||
other: This question has been asked before and already has an answer.
|
||||
not_answer:
|
||||
name:
|
||||
other: "not an answer"
|
||||
other: not an answer
|
||||
desc:
|
||||
other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
|
||||
other: This was posted as an answer, but it does not attempt to answer the
|
||||
question. It should possibly be an edit, a comment, another question,
|
||||
or deleted altogether.
|
||||
not_need:
|
||||
name:
|
||||
other: "no longer needed"
|
||||
other: no longer needed
|
||||
desc:
|
||||
other: "This comment is outdated, conversational or not relevant to this post."
|
||||
other: This comment is outdated, conversational or not relevant to this post.
|
||||
other:
|
||||
name:
|
||||
other: "something else"
|
||||
other: something else
|
||||
desc:
|
||||
other: "This post requires staff attention for another reason not listed above."
|
||||
|
||||
other: This post requires staff attention for another reason not listed above.
|
||||
question:
|
||||
close:
|
||||
duplicate:
|
||||
name:
|
||||
other: "spam"
|
||||
other: spam
|
||||
desc:
|
||||
other: "This question has been asked before and already has an answer."
|
||||
other: This question has been asked before and already has an answer.
|
||||
guideline:
|
||||
name:
|
||||
other: "a community-specific reason"
|
||||
other: a community-specific reason
|
||||
desc:
|
||||
other: "This question doesn't meet a community guideline."
|
||||
other: This question doesn't meet a community guideline.
|
||||
multiple:
|
||||
name:
|
||||
other: "needs details or clarity"
|
||||
other: needs details or clarity
|
||||
desc:
|
||||
other: "This question currently includes multiple questions in one. It should focus on one problem only."
|
||||
other: This question currently includes multiple questions in one. It should
|
||||
focus on one problem only.
|
||||
other:
|
||||
name:
|
||||
other: "something else"
|
||||
other: something else
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
other: This post requires another reason not listed above.
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
other: asked
|
||||
answered:
|
||||
other: "answered"
|
||||
other: answered
|
||||
modified:
|
||||
other: "modified"
|
||||
other: modified
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
other: "updated question"
|
||||
other: updated question
|
||||
answer_the_question:
|
||||
other: "answered question"
|
||||
other: answered question
|
||||
update_answer:
|
||||
other: "updated answer"
|
||||
other: updated answer
|
||||
accept_answer:
|
||||
other: "accepted answer"
|
||||
other: accepted answer
|
||||
comment_question:
|
||||
other: "commented question"
|
||||
other: commented question
|
||||
comment_answer:
|
||||
other: "commented answer"
|
||||
other: commented answer
|
||||
reply_to_you:
|
||||
other: "replied to you"
|
||||
other: replied to you
|
||||
mention_you:
|
||||
other: "mentioned you"
|
||||
other: mentioned you
|
||||
your_question_is_closed:
|
||||
other: "Your question has been closed"
|
||||
other: Your question has been closed
|
||||
your_question_was_deleted:
|
||||
other: "Your question has been deleted"
|
||||
other: Your question has been deleted
|
||||
your_answer_was_deleted:
|
||||
other: "Your answer has been deleted"
|
||||
other: Your answer has been deleted
|
||||
your_comment_was_deleted:
|
||||
other: "Your comment has been deleted"
|
||||
other: Your comment has been deleted
|
||||
|
||||
# The following fields are used for interface presentation(Front-end)
|
||||
ui:
|
||||
@@ -493,7 +500,7 @@ ui:
|
||||
btn_flag: Flag
|
||||
btn_save_edits: Save edits
|
||||
btn_cancel: Cancel
|
||||
show_more: Show more comment
|
||||
show_more: Show more comments
|
||||
tip_question: >-
|
||||
Use comments to ask for more information or suggest improvements. Avoid
|
||||
answering questions in comments.
|
||||
@@ -692,12 +699,12 @@ ui:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: Display name cannot be empty.
|
||||
msg_range: Display name up to 30 characters
|
||||
msg_range: Display name up to 30 characters.
|
||||
username:
|
||||
label: Username
|
||||
caption: People can mention you as "@username".
|
||||
msg: Username cannot be empty.
|
||||
msg_range: Username up to 30 characters
|
||||
msg_range: Username up to 30 characters.
|
||||
character: 'Must use the character set "a-z", "0-9", " - . _"'
|
||||
avatar:
|
||||
label: Profile Image
|
||||
@@ -871,7 +878,7 @@ ui:
|
||||
unsubscribe:
|
||||
page_title: Unsubscribe
|
||||
success_title: Unsubscribe Successful
|
||||
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||
success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
|
||||
link: Change settings
|
||||
question:
|
||||
following_tags: Following Tags
|
||||
@@ -933,7 +940,7 @@ ui:
|
||||
title: Answer
|
||||
next: Next
|
||||
done: Done
|
||||
config_yaml_error: Can’t create the config.yaml file.
|
||||
config_yaml_error: Can't create the config.yaml file.
|
||||
lang:
|
||||
label: Please Choose a Language
|
||||
db_type:
|
||||
@@ -964,7 +971,7 @@ ui:
|
||||
desc: >-
|
||||
You can create the <1>config.yaml</1> file manually in the
|
||||
<1>/var/wwww/xxx/</1> directory and paste the following text into it.
|
||||
info: "After you’ve done that, click “Next” button."
|
||||
info: After you've done that, click "Next" button.
|
||||
site_information: Site Information
|
||||
admin_account: Admin Account
|
||||
site_name:
|
||||
@@ -1027,7 +1034,7 @@ ui:
|
||||
desc: The server encountered an error and could not complete your request.
|
||||
back_home: Back to homepage
|
||||
page_maintenance:
|
||||
desc: "We are under maintenance, we’ll be back soon."
|
||||
desc: "We are under maintenance, we'll be back soon."
|
||||
nav_menus:
|
||||
dashboard: Dashboard
|
||||
contents: Contents
|
||||
@@ -1169,7 +1176,7 @@ ui:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8 - 32 characters in length.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
user_modal:
|
||||
@@ -1178,13 +1185,13 @@ ui:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: display_name must be at 4 - 30 characters in length.
|
||||
msg: Display Name must be at 3-30 characters in length.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8 - 32 characters in length.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
@@ -1305,14 +1312,14 @@ ui:
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
label: Favicon (optional)
|
||||
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
|
||||
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
|
||||
legal:
|
||||
page_title: Legal
|
||||
terms_of_service:
|
||||
|
||||
+80
-54
@@ -2,7 +2,7 @@
|
||||
backend:
|
||||
base:
|
||||
success:
|
||||
other: "Success."
|
||||
other: "Completado."
|
||||
unknown:
|
||||
other: "Error desconocido."
|
||||
request_format_error:
|
||||
@@ -88,17 +88,17 @@ backend:
|
||||
other: "Sin permiso para actualizar."
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
other: "El rango no cumple la condición."
|
||||
report:
|
||||
handle_failed:
|
||||
other: "Report handle failed."
|
||||
other: "Error en el manejador del reporte."
|
||||
not_found:
|
||||
other: "Report not found."
|
||||
other: "Informe no encontrado."
|
||||
tag:
|
||||
not_found:
|
||||
other: "Etiqueta no encontrada."
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
other: "Etiqueta recomendada no existe."
|
||||
recommend_tag_enter:
|
||||
other: "Por favor, introduce al menos una de las etiquetas requeridas."
|
||||
not_contain_synonym_tags:
|
||||
@@ -109,13 +109,13 @@ backend:
|
||||
other: "No se puede establecer como sinónimo de una etiqueta la propia etiqueta."
|
||||
smtp:
|
||||
config_from_name_cannot_be_email:
|
||||
other: "The From Name cannot be a email address."
|
||||
other: "¡Su nombre de usuario no puede ser una dirección de correo electrónico!"
|
||||
theme:
|
||||
not_found:
|
||||
other: "Tema no encontrado."
|
||||
revision:
|
||||
review_underway:
|
||||
other: "Can't edit currently, there is a version in the review queue."
|
||||
other: "No se puede editar actualmente, hay una versión en la cola de revisiones."
|
||||
no_permission:
|
||||
other: "Sin permiso para revisar."
|
||||
user:
|
||||
@@ -236,9 +236,30 @@ backend:
|
||||
#The following fields are used for interface presentation(Front-end)
|
||||
ui:
|
||||
how_to_format:
|
||||
title: How to Format
|
||||
title: Cómo formatear
|
||||
desc: >-
|
||||
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">put returns between paragraphs</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">indent code by 4 spaces</p></li><li><p class="mb-2">quote by placing <code>></code> at start of line</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">create code fences with backticks <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
|
||||
<ul class="mb-0">
|
||||
<li><p class="mb-2">Para hacer enlaces</p>
|
||||
<pre class="mb-2">
|
||||
<code><https://url.com><br/><br/>[Title](https://url.com)
|
||||
</code>
|
||||
</pre>
|
||||
</li>
|
||||
<li><p class="mb-2">Colocar saltos de línea entre párrafos</p></li>
|
||||
<li><p class="mb-2"><em>_cursiva_</em> o **<strong>negrita</strong>**</p></li>
|
||||
<li><p class="mb-2">Indentar el código con 4 espacios</p></li>
|
||||
<li><p class="mb-2">Citar colocando <code>></code> al inicio de la línea</p></li>
|
||||
<li><p class="mb-2">Escape con backticks <code>`como _esto_`</code></p></li>
|
||||
<li><p class="mb-2">Crear barreras de código con backticks <code>`</code></p>
|
||||
<pre class="mb-0">
|
||||
<code>
|
||||
```<br/>
|
||||
código aquí<br/>
|
||||
```
|
||||
</code>
|
||||
</pre>
|
||||
</li>
|
||||
</ul>
|
||||
pagination:
|
||||
prev: Anterior
|
||||
next: Siguiente
|
||||
@@ -287,13 +308,13 @@ ui:
|
||||
chart:
|
||||
text: Gráfica
|
||||
flow_chart: Diagrama de flujo
|
||||
sequence_diagram: Sequence diagram
|
||||
class_diagram: Class diagram
|
||||
state_diagram: State diagram
|
||||
entity_relationship_diagram: Entity relationship diagram
|
||||
user_defined_diagram: User defined diagram
|
||||
sequence_diagram: Diagrama de secuencia
|
||||
class_diagram: Diagrama de clase
|
||||
state_diagram: Diagrama de estado
|
||||
entity_relationship_diagram: Diagrama de relación de entidad
|
||||
user_defined_diagram: Diagrama definido por el usuario
|
||||
gantt_chart: Diagrama de Gantt
|
||||
pie_chart: Pie chart
|
||||
pie_chart: Grafico de torta
|
||||
code:
|
||||
text: Código
|
||||
add_code: Añadir código
|
||||
@@ -381,7 +402,7 @@ ui:
|
||||
heading: Encabezado
|
||||
cell: Celda
|
||||
close_modal:
|
||||
title: I am closing this post as...
|
||||
title: Estoy cerrando este post como...
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Enviar
|
||||
remark:
|
||||
@@ -389,11 +410,11 @@ ui:
|
||||
msg:
|
||||
empty: Por favor selecciona una razón.
|
||||
report_modal:
|
||||
flag_title: I am flagging to report this post as...
|
||||
close_title: I am closing this post as...
|
||||
review_question_title: Review question
|
||||
review_answer_title: Review answer
|
||||
review_comment_title: Review comment
|
||||
flag_title: Estoy marcando este post como...
|
||||
close_title: Estoy cerrando este post como...
|
||||
review_question_title: Revisar pregunta
|
||||
review_answer_title: Revisar respuesta
|
||||
review_comment_title: Revisar comentario
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Enviar
|
||||
remark:
|
||||
@@ -408,7 +429,7 @@ ui:
|
||||
label: Nombre a mostrar
|
||||
msg:
|
||||
empty: El nombre a mostrar no puede estar vacío.
|
||||
range: Display name up to 35 characters.
|
||||
range: Nombre a mostrar con un máximo de 35 caracteres.
|
||||
slug_name:
|
||||
label: URL amigable
|
||||
desc: 'Debe usar el conjunto de caracteres "a-z", "0-9", "+ # - ."'
|
||||
@@ -426,16 +447,16 @@ ui:
|
||||
history: Historial
|
||||
synonyms:
|
||||
title: Sinónimos
|
||||
text: The following tags will be remapped to
|
||||
text: Las siguientes etiquetas serán reasignadas a
|
||||
empty: No se encontraron sinónimos.
|
||||
btn_add: Añadir un sinónimo
|
||||
btn_edit: Editar
|
||||
btn_save: Guardar
|
||||
synonyms_text: The following tags will be remapped to
|
||||
synonyms_text: Las siguientes etiquetas serán reasignadas a
|
||||
delete:
|
||||
title: Eliminar esta etiqueta
|
||||
content: >-
|
||||
<p>We do not allowed deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
|
||||
<p>No se permite la eliminación de etiquetas con posts.</p><p>Por favor antes elimina esta etiqueta del post.</p>
|
||||
content2: '¿Estás seguro de que deseas borrarlo?'
|
||||
close: Cerrar
|
||||
edit_tag:
|
||||
@@ -508,16 +529,16 @@ ui:
|
||||
tag_label: preguntas
|
||||
search_placeholder: Filtrar por nombre de etiqueta
|
||||
no_desc: La etiqueta no tiene descripción.
|
||||
more: More
|
||||
more: Mas
|
||||
ask:
|
||||
title: Add Question
|
||||
edit_title: Edit Question
|
||||
default_reason: Edit question
|
||||
title: Agregar una pregunta
|
||||
edit_title: Editar pregunta
|
||||
default_reason: Editar pregunta
|
||||
similar_questions: Preguntas similares
|
||||
form:
|
||||
fields:
|
||||
revision:
|
||||
label: Revision
|
||||
label: Revisión
|
||||
title:
|
||||
label: Título
|
||||
placeholder: Sé preciso e imagina que le estás preguntando esto a una persona
|
||||
@@ -585,7 +606,8 @@ ui:
|
||||
msg:
|
||||
empty: No puede estar en blanco.
|
||||
login:
|
||||
page_title: Bienvenido a Answer
|
||||
page_title: Bienvenido a {{site_name}}
|
||||
login_to_continue: Inicia sesión para continuar
|
||||
info_sign: '¿No tienes cuenta? <1>Regístrate</1>'
|
||||
info_login: '¿Ya tienes una cuenta? <1>Inicia sesión</1>'
|
||||
agreements: Al registrarte, aceptas la <1>política de privacidad</1> y los <3>términos de servicio</3>.
|
||||
@@ -666,7 +688,7 @@ ui:
|
||||
custom: Propia
|
||||
btn_refresh: Actualizar
|
||||
custom_text: Puedes subir tu propia imagen.
|
||||
default: System
|
||||
default: Sistema
|
||||
msg: Por favor, sube una imagen
|
||||
bio:
|
||||
label: Sobre mí (opcional)
|
||||
@@ -693,11 +715,11 @@ ui:
|
||||
msg: El correo electrónico no puede estar vacío.
|
||||
password_title: Password
|
||||
current_pass:
|
||||
label: Current Password
|
||||
label: Contraseña actual
|
||||
msg:
|
||||
empty: Current Password cannot be empty.
|
||||
length: The length needs to be between 8 and 32.
|
||||
different: The two entered passwords do not match.
|
||||
empty: La contraseña actual no puede estar vacía.
|
||||
length: El largo necesita estar entre 8 y 32 caracteres.
|
||||
different: Las contraseñas no coinciden.
|
||||
new_pass:
|
||||
label: Nueva Contraseña
|
||||
pass_confirm:
|
||||
@@ -712,7 +734,7 @@ ui:
|
||||
update_password: Contraseña cambiada con éxito.
|
||||
flag_success: Gracias por reportar.
|
||||
forbidden_operate_self: No puedes modificar tu propio usuario
|
||||
review: Your revision will show after review.
|
||||
review: Tu revisión será visible luego de ser aprobada.
|
||||
related_question:
|
||||
title: Preguntas relacionadas
|
||||
btn: Añadir pregunta
|
||||
@@ -726,7 +748,7 @@ ui:
|
||||
Follow: Seguir
|
||||
Following: Siguiendo
|
||||
answered: respondida
|
||||
closed_in: Closed in
|
||||
closed_in: Cerrado el
|
||||
show_exist: Mostrar una pregunta existente.
|
||||
answers:
|
||||
title: Respuestas
|
||||
@@ -750,9 +772,13 @@ ui:
|
||||
delete:
|
||||
title: Eliminar esta publicación
|
||||
question: >-
|
||||
We do not recommend <strong>deleting questions with answers</strong> because doing so deprives future readers of this knowledge.</p><p>Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
|
||||
No recomendamos <strong>borrar preguntas con respuestas</strong> porque esto priva a los lectores futuros de este conocimiento.
|
||||
</p><p>
|
||||
El borrado repetido de preguntas respondidas puede resultar en que tu cuenta se bloquee para hacer preguntas. ¿Estás seguro de que deseas borrarlo?
|
||||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
<p>No recomendamos <strong>borrar la respuesta aceptada</strong> porque esto priva a los lectores futuros de este conocimiento.</p>
|
||||
|
||||
El borrado repetido de respuestas aceptadas puede resultar en que tu cuenta se bloquee para responder. ¿Estás seguro de que deseas borrarlo?
|
||||
other: '¿Estás seguro de que deseas borrarlo?'
|
||||
tip_question_deleted: Esta publicación ha sido eliminada
|
||||
tip_answer_deleted: Esta respuesta ha sido eliminada
|
||||
@@ -782,13 +808,13 @@ ui:
|
||||
newest: Más reciente
|
||||
active: Activas
|
||||
score: Puntuación
|
||||
more: More
|
||||
more: Mas
|
||||
tips:
|
||||
title: Advanced Search Tips
|
||||
tag: "<1>[tag]</1> search withing a tag"
|
||||
user: "<1>user:username</1> search by author"
|
||||
answer: "<1>answers:0</1> unanswered questions"
|
||||
score: "<1>score:3</1> posts with a 3+ score"
|
||||
title: Consejos de búsqueda avanzada
|
||||
tag: "<1>[tag]</1> búsqueda por etiquetas"
|
||||
user: "<1>user:username</1> búsqueda por autor"
|
||||
answer: "<1>answers:0</1> preguntas sin responder"
|
||||
score: "<1>score:3</1> Publicaciones con un puntaje de 3 o más"
|
||||
question: "<1>is:question</1> buscar preguntas"
|
||||
is_answer: "<1>is:answer</1> buscar respuestas"
|
||||
empty: No pudimos encontrar nada. <br /> Prueba a buscar con palabras diferentes o menos específicas.
|
||||
@@ -810,14 +836,14 @@ ui:
|
||||
Lo sentimos, este link de confirmación de cuenta ya no es válido. ¿Quizás tu cuenta ya está activa?
|
||||
confirm_new_email: Tu email ha sido actualizado.
|
||||
confirm_new_email_invalid: >-
|
||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||
Lo siento, este enlace de confirmación ya no es válido. ¿Quizás ya se haya cambiado tu correo electrónico?
|
||||
unsubscribe:
|
||||
page_title: Desuscribir
|
||||
success_title: Desuscrito con éxito
|
||||
success_desc: Has sido eliminado con éxito de esta lista de suscriptores y no recibirás más correos electrónicos nuestros.
|
||||
link: Cambiar ajustes
|
||||
question:
|
||||
following_tags: Following Tags
|
||||
following_tags: Etiquetas seguidas
|
||||
edit: Editar
|
||||
save: Guardar
|
||||
follow_tag_tip: Sigue etiquetas para personalizar tu lista de preguntas.
|
||||
@@ -828,8 +854,8 @@ ui:
|
||||
questions: Preguntas
|
||||
answers: Respuestas
|
||||
newest: Más reciente
|
||||
active: Active
|
||||
frequent: Frequent
|
||||
active: Activo
|
||||
frequent: Frecuente
|
||||
score: Puntuación
|
||||
unanswered: Sin respuesta
|
||||
modified: modificada
|
||||
@@ -852,8 +878,8 @@ ui:
|
||||
score: Puntuación
|
||||
edit_profile: Editar perfil
|
||||
visited_x_days: "Visitado {{ count }} días"
|
||||
viewed: Viewed
|
||||
joined: Joined
|
||||
viewed: Visto
|
||||
joined: Unido
|
||||
last_login: Seen
|
||||
about_me: Sobre mí
|
||||
about_me_empty: "// ¡Hola Mundo!"
|
||||
@@ -1222,14 +1248,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+393
-392
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+16
-16
@@ -1,26 +1,26 @@
|
||||
# all support language
|
||||
language_options:
|
||||
- label: "English(US)"
|
||||
value: "en_US"
|
||||
- label: "Español(ES)"
|
||||
value: "es_ES"
|
||||
- label: "Português(PT)"
|
||||
value: "pt_PT"
|
||||
- label: "Deutsch(DE)"
|
||||
value: "de_DE"
|
||||
- label: "Français(FR)"
|
||||
value: "fr_FR"
|
||||
- label: "日本語(JA)"
|
||||
value: "ja_JP"
|
||||
- label: "Italiano(IT)"
|
||||
value: "it_IT"
|
||||
- label: "Русский(RU)"
|
||||
value: "ru_RU"
|
||||
- label: "简体中文(CN)"
|
||||
value: "zh_CN"
|
||||
- label: "繁體中文(CN)"
|
||||
value: "zh_TW"
|
||||
- label: "English(US)"
|
||||
value: "en_US"
|
||||
- label: "Deutsch(DE)"
|
||||
value: "de_DE"
|
||||
- label: "Español(ES)"
|
||||
value: "es_ES"
|
||||
- label: "Français(FR)"
|
||||
value: "fr_FR"
|
||||
- label: "Italiano(IT)"
|
||||
value: "it_IT"
|
||||
- label: "日本語(JA)"
|
||||
value: "ja_JP"
|
||||
- label: "한국어(KO)"
|
||||
value: "ko_KR"
|
||||
- label: "Português(PT)"
|
||||
value: "pt_PT"
|
||||
- label: "Русский(RU)"
|
||||
value: "ru_RU"
|
||||
- label: "Tiếng Việt(VI)"
|
||||
value: "vi_VN"
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -129,7 +129,7 @@ backend:
|
||||
username_invalid:
|
||||
other: "utente non valido"
|
||||
username_duplicate:
|
||||
other: "utente già in uso"
|
||||
other: "Nome utente già in uso"
|
||||
set_avatar:
|
||||
other: "Inserimento dell'Avatar non riuscito."
|
||||
cannot_update_your_role:
|
||||
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+4
-3
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+4
-3
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+152
-151
@@ -2,122 +2,122 @@
|
||||
backend:
|
||||
base:
|
||||
success:
|
||||
other: "Success."
|
||||
other: "Выполнено."
|
||||
unknown:
|
||||
other: "Unknown error."
|
||||
other: "Неизвестная ошибка."
|
||||
request_format_error:
|
||||
other: "Request format is not valid."
|
||||
other: "Формат файла не корректен."
|
||||
unauthorized_error:
|
||||
other: "Unauthorized."
|
||||
other: "Авторизация не выполнена."
|
||||
database_error:
|
||||
other: "Data server error."
|
||||
other: "Ошибка сервера данных."
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
other: "User"
|
||||
other: "Пользователь"
|
||||
admin:
|
||||
other: "Admin"
|
||||
other: "Администратор"
|
||||
moderator:
|
||||
other: "Moderator"
|
||||
other: "Модератор"
|
||||
description:
|
||||
user:
|
||||
other: "Default with no special access."
|
||||
other: "По умолчанию, без специального доступа."
|
||||
admin:
|
||||
other: "Have the full power to access the site."
|
||||
other: "Имейте все полномочия для доступа к сайту."
|
||||
moderator:
|
||||
other: "Has access to all posts except admin settings."
|
||||
other: "Имеет доступ ко всем сообщениям, кроме настроек администратора."
|
||||
email:
|
||||
other: "Email"
|
||||
other: "Эл. почта"
|
||||
password:
|
||||
other: "Password"
|
||||
other: "Пароль"
|
||||
email_or_password_wrong_error:
|
||||
other: "Email and password do not match."
|
||||
other: "Неверное имя пользователя или пароль."
|
||||
error:
|
||||
admin:
|
||||
email_or_password_wrong:
|
||||
other: Email and password do not match.
|
||||
other: Неверное имя пользователя или пароль.
|
||||
answer:
|
||||
not_found:
|
||||
other: "Answer do not found."
|
||||
other: "Ответ не найден."
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "Недостаточно прав для удаления."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Нет прав для обновления."
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "Comment are not allowed to edit."
|
||||
other: "Комментарий не может редактироваться."
|
||||
not_found:
|
||||
other: "Comment not found."
|
||||
other: "Комментарий не найден."
|
||||
email:
|
||||
duplicate:
|
||||
other: "Email already exists."
|
||||
other: "Адрес электронной почты уже существует."
|
||||
need_to_be_verified:
|
||||
other: "Email should be verified."
|
||||
other: "Адрес электронной почты должен быть подтвержден."
|
||||
verify_url_expired:
|
||||
other: "Email verified URL has expired, please resend the email."
|
||||
other: "Срок действия подтверждённого адреса электронной почты истек, пожалуйста, отправьте письмо повторно."
|
||||
lang:
|
||||
not_found:
|
||||
other: "Language file not found."
|
||||
other: "Языковой файл не найден."
|
||||
object:
|
||||
captcha_verification_failed:
|
||||
other: "Captcha wrong."
|
||||
other: "Captcha введена неверно."
|
||||
disallow_follow:
|
||||
other: "You are not allowed to follow."
|
||||
other: "Вы не можете подписаться."
|
||||
disallow_vote:
|
||||
other: "You are not allowed to vote."
|
||||
other: "Вы не можете голосовать."
|
||||
disallow_vote_your_self:
|
||||
other: "You can't vote for your own post."
|
||||
other: "Вы не можете голосовать за собственный отзыв."
|
||||
not_found:
|
||||
other: "Object not found."
|
||||
other: "Объект не найден."
|
||||
verification_failed:
|
||||
other: "Verification failed."
|
||||
other: "Проверка не удалась."
|
||||
email_or_password_incorrect:
|
||||
other: "Email and password do not match."
|
||||
other: "Email или пароль не совпадают."
|
||||
old_password_verification_failed:
|
||||
other: "The old password verification failed"
|
||||
other: "Не удалось подтвердить старый пароль"
|
||||
new_password_same_as_previous_setting:
|
||||
other: "The new password is the same as the previous one."
|
||||
other: "Пароль не может быть таким же как прежний."
|
||||
question:
|
||||
not_found:
|
||||
other: "Question not found."
|
||||
other: "Вопрос не найден."
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "Недостаточно прав для удаления."
|
||||
cannot_close:
|
||||
other: "No permission to close."
|
||||
other: "Нет разрешения на закрытие."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Нет разрешения на обновление."
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
other: "Ранг не соответствует условию."
|
||||
report:
|
||||
handle_failed:
|
||||
other: "Report handle failed."
|
||||
other: "Не удалось обработать отчет."
|
||||
not_found:
|
||||
other: "Report not found."
|
||||
other: "Отчет не найден."
|
||||
tag:
|
||||
not_found:
|
||||
other: "Tag not found."
|
||||
other: "Тег не найден."
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
other: "Рекомендуемый тег не существует."
|
||||
recommend_tag_enter:
|
||||
other: "Please enter at least one required tag."
|
||||
other: "Пожалуйста, введите хотя бы один тег."
|
||||
not_contain_synonym_tags:
|
||||
other: "Should not contain synonym tags."
|
||||
other: "Не должно содержать теги синонимы."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Нет прав для обновления."
|
||||
cannot_set_synonym_as_itself:
|
||||
other: "You cannot set the synonym of the current tag as itself."
|
||||
other: "Вы не можете установить синоним текущего тега."
|
||||
smtp:
|
||||
config_from_name_cannot_be_email:
|
||||
other: "The From Name cannot be a email address."
|
||||
other: "Имя пользователя не может быть адрес электронной почты."
|
||||
theme:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
other: "Тема не найдена."
|
||||
revision:
|
||||
review_underway:
|
||||
other: "Can't edit currently, there is a version in the review queue."
|
||||
other: "В настоящее время не удается редактировать версию, в очереди на проверку."
|
||||
no_permission:
|
||||
other: "No permission to Revision."
|
||||
other: "Нет прав на ревизию."
|
||||
user:
|
||||
email_or_password_wrong:
|
||||
other:
|
||||
@@ -241,68 +241,68 @@ ui:
|
||||
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">put returns between paragraphs</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">indent code by 4 spaces</p></li><li><p class="mb-2">quote by placing <code>></code> at start of line</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">create code fences with backticks <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
|
||||
pagination:
|
||||
prev: Prev
|
||||
next: Next
|
||||
next: Следующий
|
||||
page_title:
|
||||
question: Question
|
||||
questions: Questions
|
||||
tag: Tag
|
||||
tags: Tags
|
||||
tag_wiki: tag wiki
|
||||
edit_tag: Edit Tag
|
||||
ask_a_question: Add Question
|
||||
edit_question: Edit Question
|
||||
edit_answer: Edit Answer
|
||||
search: Search
|
||||
posts_containing: Posts containing
|
||||
settings: Settings
|
||||
notifications: Notifications
|
||||
login: Log In
|
||||
sign_up: Sign Up
|
||||
account_recovery: Account Recovery
|
||||
account_activation: Account Activation
|
||||
confirm_email: Confirm Email
|
||||
account_suspended: Account Suspended
|
||||
admin: Admin
|
||||
change_email: Modify Email
|
||||
install: Answer Installation
|
||||
upgrade: Answer Upgrade
|
||||
maintenance: Website Maintenance
|
||||
users: Users
|
||||
question: Вопрос
|
||||
questions: Вопросы
|
||||
tag: Тэг
|
||||
tags: Теги
|
||||
tag_wiki: wiki тэг
|
||||
edit_tag: Изменить тег
|
||||
ask_a_question: Добавить вопрос
|
||||
edit_question: Редактировать вопрос
|
||||
edit_answer: Редактировать ответ
|
||||
search: Поиск
|
||||
posts_containing: Посты содержащие
|
||||
settings: Настройки
|
||||
notifications: Уведомления
|
||||
login: Вход
|
||||
sign_up: Регистрация
|
||||
account_recovery: Восстановление аккаунта
|
||||
account_activation: Активация учётной записи
|
||||
confirm_email: Подтвердить адрес электронной почты
|
||||
account_suspended: Аккаунт заблокирован
|
||||
admin: Управление
|
||||
change_email: Изменить Email
|
||||
install: Установка ответа
|
||||
upgrade: Обновить ответ
|
||||
maintenance: Обслуживание сайта
|
||||
users: Пользователи
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
achievement: Achievements
|
||||
all_read: Mark all as read
|
||||
show_more: Show more
|
||||
title: Уведомления
|
||||
inbox: Входящие
|
||||
achievement: Достижения
|
||||
all_read: Отметить всё как прочитанное
|
||||
show_more: Показать еще
|
||||
suspended:
|
||||
title: Your Account has been Suspended
|
||||
until_time: "Your account was suspended until {{ time }}."
|
||||
forever: This user was suspended forever.
|
||||
end: You don't meet a community guideline.
|
||||
title: Ваш аккаунт заблокирован
|
||||
until_time: "Ваша учетная запись была заблокирована до {{ time }}."
|
||||
forever: Этот пользователь был навсегда заблокирован.
|
||||
end: Вы не соответствуете правилам сообщества.
|
||||
editor:
|
||||
blockquote:
|
||||
text: Blockquote
|
||||
text: Цитата
|
||||
bold:
|
||||
text: Strong
|
||||
text: Жирный
|
||||
chart:
|
||||
text: Chart
|
||||
flow_chart: Flow chart
|
||||
sequence_diagram: Sequence diagram
|
||||
class_diagram: Class diagram
|
||||
state_diagram: State diagram
|
||||
entity_relationship_diagram: Entity relationship diagram
|
||||
user_defined_diagram: User defined diagram
|
||||
gantt_chart: Gantt chart
|
||||
pie_chart: Pie chart
|
||||
text: Диаграмма
|
||||
flow_chart: Блок-схема
|
||||
sequence_diagram: Диаграмма последовательности
|
||||
class_diagram: Диаграмма классов
|
||||
state_diagram: Диаграмма состояний
|
||||
entity_relationship_diagram: Диаграмма связей сущностей
|
||||
user_defined_diagram: Пользовательская диаграмма
|
||||
gantt_chart: Диаграмма Гантта
|
||||
pie_chart: Круговая диаграмма
|
||||
code:
|
||||
text: Code Sample
|
||||
add_code: Add code sample
|
||||
text: Фрагмент кода
|
||||
add_code: Добавить пример кода
|
||||
form:
|
||||
fields:
|
||||
code:
|
||||
label: Code
|
||||
label: Код
|
||||
msg:
|
||||
empty: Code cannot be empty.
|
||||
empty: Код не может быть пустым.
|
||||
language:
|
||||
label: Language (optional)
|
||||
placeholder: Automatic detection
|
||||
@@ -385,79 +385,79 @@ ui:
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
remark:
|
||||
empty: Cannot be empty.
|
||||
empty: Не может быть пустым.
|
||||
msg:
|
||||
empty: Please select a reason.
|
||||
empty: Пожалуйста, выбери причину.
|
||||
report_modal:
|
||||
flag_title: I am flagging to report this post as...
|
||||
close_title: I am closing this post as...
|
||||
review_question_title: Review question
|
||||
review_answer_title: Review answer
|
||||
review_comment_title: Review comment
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
flag_title: Я помечаю, чтобы пожаловаться на эту публикацию как...
|
||||
close_title: Я закрываю этот пост как...
|
||||
review_question_title: Проверить вопрос
|
||||
review_answer_title: Проверить ответ
|
||||
review_comment_title: Просмотр комментариев
|
||||
btn_cancel: Отмена
|
||||
btn_submit: Отправить
|
||||
remark:
|
||||
empty: Cannot be empty.
|
||||
empty: Не может быть пустым.
|
||||
msg:
|
||||
empty: Please select a reason.
|
||||
empty: Пожалуйста, выбери причину.
|
||||
tag_modal:
|
||||
title: Create new tag
|
||||
title: Создать новый тег
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
label: Показывать имя
|
||||
msg:
|
||||
empty: Display name cannot be empty.
|
||||
range: Display name up to 35 characters.
|
||||
empty: Отображаемое название не может быть пустым.
|
||||
range: Отображаемое имя до 35 символов.
|
||||
slug_name:
|
||||
label: URL Slug
|
||||
desc: 'Must use the character set "a-z", "0-9", "+ # - ."'
|
||||
label: Идентификатор URL
|
||||
desc: 'Необходимо использовать набор символов "a-z", "0-9", "+ # - ."'
|
||||
msg:
|
||||
empty: URL slug cannot be empty.
|
||||
range: URL slug up to 35 characters.
|
||||
character: URL slug contains unallowed character set.
|
||||
empty: URL не может быть пустым.
|
||||
range: URL slug до 35 символов.
|
||||
character: URL slug содержит недопустимый набор символов.
|
||||
desc:
|
||||
label: Description (optional)
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: Описание (опционально)
|
||||
btn_cancel: Отмена
|
||||
btn_submit: Отправить
|
||||
tag_info:
|
||||
created_at: Created
|
||||
edited_at: Edited
|
||||
history: History
|
||||
created_at: Создано
|
||||
edited_at: Отредактировано
|
||||
history: История
|
||||
synonyms:
|
||||
title: Synonyms
|
||||
text: The following tags will be remapped to
|
||||
empty: No synonyms found.
|
||||
btn_add: Add a synonym
|
||||
btn_edit: Edit
|
||||
btn_save: Save
|
||||
synonyms_text: The following tags will be remapped to
|
||||
title: Синонимы
|
||||
text: Следующие теги будут переназначены на
|
||||
empty: Синонимы не найдены.
|
||||
btn_add: Добавить синоним
|
||||
btn_edit: Редактировать
|
||||
btn_save: Сохранить
|
||||
synonyms_text: Следующие теги будут переназначены на
|
||||
delete:
|
||||
title: Delete this tag
|
||||
title: Удалить этот тег
|
||||
content: >-
|
||||
<p>We do not allowed deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
|
||||
content2: Are you sure you wish to delete?
|
||||
close: Close
|
||||
<p>Мы не разрешаем удалять тег с сообщениями.</p><p>Пожалуйста, сначала удалите этот тег из сообщений</p>
|
||||
content2: Вы действительно хотите удалить?
|
||||
close: Закрыть
|
||||
edit_tag:
|
||||
title: Edit Tag
|
||||
default_reason: Edit tag
|
||||
title: Изменить тег
|
||||
default_reason: Правка тега
|
||||
form:
|
||||
fields:
|
||||
revision:
|
||||
label: Revision
|
||||
label: Редакция
|
||||
display_name:
|
||||
label: Display Name
|
||||
label: Показывать имя
|
||||
slug_name:
|
||||
label: URL Slug
|
||||
info: 'Must use the character set "a-z", "0-9", "+ # - ."'
|
||||
label: Идентификатор URL
|
||||
info: 'Необходимо использовать набор символов "a-z", "0-9", "+ # - ."'
|
||||
desc:
|
||||
label: Description
|
||||
label: Описание
|
||||
edit_summary:
|
||||
label: Edit Summary
|
||||
label: Изменить краткое описание
|
||||
placeholder: >-
|
||||
Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
|
||||
btn_save_edits: Save edits
|
||||
btn_cancel: Cancel
|
||||
Кратко опишите ваши изменения (исправленная орфография, исправленная грамматика, улучшенное форматирование)
|
||||
btn_save_edits: Сохранить изменения
|
||||
btn_cancel: Отмена
|
||||
dates:
|
||||
long_date: MMM D
|
||||
long_date_with_year: "MMM D, YYYY"
|
||||
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+1353
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
+18
-14
@@ -405,17 +405,17 @@ ui:
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: 显示名称(别名)
|
||||
label: 显示名称
|
||||
msg:
|
||||
empty: 不能为空
|
||||
range: 不能超过 35 个字符
|
||||
empty: 显示名称不能为空。
|
||||
range: 显示名称不能超过 35 个字符。
|
||||
slug_name:
|
||||
label: URL 固定链接
|
||||
desc: '必须使用字符集 "a-z"、"0-9"、"+ # - ."'
|
||||
desc: '必须由 "a-z", "0-9", "+ # - ." 组成'
|
||||
msg:
|
||||
empty: 不能为空
|
||||
range: 不能超过 35 个字符
|
||||
character: 包含非法字符
|
||||
empty: URL 固定链接不能为空。
|
||||
range: URL 固定链接不能超过 35 个字符。
|
||||
character: URL 固定链接包含非法字符。
|
||||
desc:
|
||||
label: 描述(可选)
|
||||
btn_cancel: 取消
|
||||
@@ -446,7 +446,7 @@ ui:
|
||||
revision:
|
||||
label: 编辑历史
|
||||
display_name:
|
||||
label: 名称
|
||||
label: 显示名称
|
||||
slug_name:
|
||||
label: URL 固定链接
|
||||
info: '必须由 "a-z", "0-9", "+ # - ." 组成'
|
||||
@@ -585,6 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: 不能为空
|
||||
login:
|
||||
page_title: 欢迎来到 {{site_name}}
|
||||
login_to_continue: 登录以继续
|
||||
info_sign: 没有账户?<1>注册</1>
|
||||
info_login: 已经有一个账户?<1>登录</1>
|
||||
agreements: 登录即表示您同意<1>隐私政策</1>和<3>服务条款</3>。
|
||||
@@ -613,6 +615,7 @@ ui:
|
||||
msg:
|
||||
empty: 邮箱不能为空
|
||||
change_email:
|
||||
page_title: 欢迎来到 {{site_name}}
|
||||
btn_cancel: 取消
|
||||
btn_update: 更新电子邮件地址
|
||||
send_success: >-
|
||||
@@ -649,8 +652,8 @@ ui:
|
||||
btn_name: 保存
|
||||
display_name:
|
||||
label: 昵称
|
||||
msg: 昵称不能为空
|
||||
msg_range: 昵称不能超过 30 个字符
|
||||
msg: 昵称不能为空。
|
||||
msg_range: 昵称不能超过 30 个字符。
|
||||
username:
|
||||
label: 用户名
|
||||
caption: 用户之间可以通过 "@用户名" 进行交互。
|
||||
@@ -801,6 +804,7 @@ ui:
|
||||
modal_confirm:
|
||||
title: 发生错误...
|
||||
account_result:
|
||||
page_title: 欢迎来到 {{site_name}}
|
||||
success: 你的账号已通过验证,即将返回首页。
|
||||
link: 返回首页
|
||||
invalid: >-
|
||||
@@ -1100,7 +1104,7 @@ ui:
|
||||
fields:
|
||||
display_name:
|
||||
label: 昵称
|
||||
msg: 昵称的长度必须是4-30个字符。
|
||||
msg: 昵称的长度必须是 3-30 个字符。
|
||||
email:
|
||||
label: 邮箱
|
||||
msg: 电子邮箱无效。
|
||||
@@ -1220,14 +1224,14 @@ ui:
|
||||
branding:
|
||||
page_title: 品牌
|
||||
logo:
|
||||
label: 图标
|
||||
label: Logo (可选)
|
||||
msg: 图标不能为空。
|
||||
text: 在你的网站左上方的Logo图标。使用一个高度为56,长宽比大于3:1的宽长方形图像。如果留空,将显示网站标题文本。
|
||||
mobile_logo:
|
||||
label: 移动端图标(可选)
|
||||
text: 在你的网站的移动版上使用的标志。使用一个高度为56的宽矩形图像。如果留空,将使用 "Logo"设置中的图像。
|
||||
square_icon:
|
||||
label: 方形图标
|
||||
label: 方形图标 (可选)
|
||||
msg: 方形图标不能为空。
|
||||
text: 用作元数据图标的基础的图像。最好是大于512x512。
|
||||
favicon:
|
||||
@@ -1245,7 +1249,7 @@ ui:
|
||||
page_title: 编辑
|
||||
recommend_tags:
|
||||
label: 推荐标签
|
||||
text: "请输入以上标签,每行一个标签。"
|
||||
text: "请在上方输入标签固定链接,每行一个标签。"
|
||||
required_tag:
|
||||
title: 必需的标签
|
||||
label: 根据需要设置推荐标签
|
||||
|
||||
+27
-26
@@ -10,24 +10,24 @@ backend:
|
||||
unauthorized_error:
|
||||
other: "未授權。"
|
||||
database_error:
|
||||
other: "Data server error."
|
||||
other: "資料庫錯誤。"
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
other: "User"
|
||||
other: "使用者"
|
||||
admin:
|
||||
other: "Admin"
|
||||
other: "管理者"
|
||||
moderator:
|
||||
other: "Moderator"
|
||||
other: "版主"
|
||||
description:
|
||||
user:
|
||||
other: "Default with no special access."
|
||||
other: "預設沒有特別閱讀權限"
|
||||
admin:
|
||||
other: "Have the full power to access the site."
|
||||
other: "擁有所有權限"
|
||||
moderator:
|
||||
other: "Has access to all posts except admin settings."
|
||||
other: "可以訪問除了管理員設定以外的所有貼文"
|
||||
email:
|
||||
other: "Email"
|
||||
other: "電子郵件"
|
||||
password:
|
||||
other: "密碼"
|
||||
email_or_password_wrong_error:
|
||||
@@ -38,37 +38,37 @@ backend:
|
||||
other: 電子郵箱和密碼不匹配。
|
||||
answer:
|
||||
not_found:
|
||||
other: "Answer do not found."
|
||||
other: "無答案。"
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "無刪除權限。"
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "無更新權限。"
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "Comment are not allowed to edit."
|
||||
other: "不允許編輯評論。"
|
||||
not_found:
|
||||
other: "Comment not found."
|
||||
other: "無評論。"
|
||||
email:
|
||||
duplicate:
|
||||
other: "Email already exists."
|
||||
other: "該電子郵件已被使用"
|
||||
need_to_be_verified:
|
||||
other: "Email should be verified."
|
||||
other: "需驗證電子郵件信箱。"
|
||||
verify_url_expired:
|
||||
other: "Email verified URL has expired, please resend the email."
|
||||
other: "電子郵件驗證網址已過期,請重發確認郵件。"
|
||||
lang:
|
||||
not_found:
|
||||
other: "Language file not found."
|
||||
other: "無此語系檔。"
|
||||
object:
|
||||
captcha_verification_failed:
|
||||
other: "Captcha wrong."
|
||||
other: "驗證碼錯誤。"
|
||||
disallow_follow:
|
||||
other: "You are not allowed to follow."
|
||||
other: "你不能追蹤"
|
||||
disallow_vote:
|
||||
other: "You are not allowed to vote."
|
||||
other: "你不能投票"
|
||||
disallow_vote_your_self:
|
||||
other: "You can't vote for your own post."
|
||||
other: "你不能為自己的貼文投票"
|
||||
not_found:
|
||||
other: "Object not found."
|
||||
other: "找不到物件"
|
||||
verification_failed:
|
||||
other: "驗證失敗。"
|
||||
email_or_password_incorrect:
|
||||
@@ -88,7 +88,7 @@ backend:
|
||||
other: "沒有更新的權限。"
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
other: "無法為條件排序"
|
||||
report:
|
||||
handle_failed:
|
||||
other: "Report handle failed."
|
||||
@@ -585,7 +585,8 @@ ui:
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to Answer
|
||||
page_title: Welcome to {{site_name}}
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
@@ -1222,14 +1223,14 @@ ui:
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
label: Logo
|
||||
label: Logo (optional)
|
||||
msg: Logo cannot be empty.
|
||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
label: Square Icon (optional)
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
favicon:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package constant
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
CommentEditDeadline = time.Minute * 5
|
||||
)
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"github.com/answerdev/answer/pkg/dir"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/segmentfault/pacman/cache"
|
||||
"github.com/segmentfault/pacman/contrib/cache/memory"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
_ "modernc.org/sqlite"
|
||||
"xorm.io/core"
|
||||
"xorm.io/xorm"
|
||||
ormlog "xorm.io/xorm/log"
|
||||
@@ -38,11 +38,13 @@ func NewDB(debug bool, dataConf *Database) (*xorm.Engine, error) {
|
||||
dataConf.Driver = string(schemas.MYSQL)
|
||||
}
|
||||
if dataConf.Driver == string(schemas.SQLITE) {
|
||||
dataConf.Driver = "sqlite"
|
||||
dbFileDir := filepath.Dir(dataConf.Connection)
|
||||
log.Debugf("try to create database directory %s", dbFileDir)
|
||||
if err := dir.CreateDirIfNotExist(dbFileDir); err != nil {
|
||||
log.Errorf("create database dir failed: %s", err)
|
||||
}
|
||||
dataConf.MaxOpenConn = 1
|
||||
}
|
||||
engine, err := xorm.NewEngine(dataConf.Driver, dataConf.Connection)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -53,6 +54,8 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
ext := strings.ToLower(path.Ext(filePath)[1:])
|
||||
ctx.Header("content-type", fmt.Sprintf("image/%s", ext))
|
||||
_, err = ctx.Writer.WriteString(string(avatarfile))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -60,6 +63,17 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||
ctx.Abort()
|
||||
return
|
||||
|
||||
} else {
|
||||
uUrl, err := url.Parse(u)
|
||||
if err != nil {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
_, urlfileName := filepath.Split(uUrl.Path)
|
||||
uploadPath := am.serviceConfig.UploadPath
|
||||
filePath := fmt.Sprintf("%s/%s", uploadPath, urlfileName)
|
||||
ext := strings.ToLower(path.Ext(filePath)[1:])
|
||||
ctx.Header("content-type", fmt.Sprintf("image/%s", ext))
|
||||
}
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
const (
|
||||
EmailOrPasswordWrong = "error.object.email_or_password_incorrect"
|
||||
CommentNotFound = "error.comment.not_found"
|
||||
CommentCannotEditAfterDeadline = "error.comment.cannot_edit_after_deadline"
|
||||
QuestionNotFound = "error.question.not_found"
|
||||
QuestionCannotDeleted = "error.question.cannot_deleted"
|
||||
QuestionCannotClose = "error.question.cannot_close"
|
||||
@@ -53,6 +54,7 @@ const (
|
||||
InstallConfigFailed = "error.install.create_config_failed"
|
||||
SiteInfoNotFound = "error.site_info.not_found"
|
||||
UploadFileSourceUnsupported = "error.upload.source_unsupported"
|
||||
UploadFileUnsupportedFileFormat = "error.upload.unsupported_file_format"
|
||||
RecommendTagNotExist = "error.tag.recommend_tag_not_found"
|
||||
RecommendTagEnter = "error.tag.recommend_tag_enter"
|
||||
RevisionReviewUnderway = "error.revision.review_underway"
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/google/wire"
|
||||
myTran "github.com/segmentfault/pacman/contrib/i18n"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -68,12 +69,14 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
|
||||
|
||||
content, err := yaml.Marshal(translation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal translation content failed: %s %s", file.Name(), err)
|
||||
log.Debugf("marshal translation content failed: %s %s", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
// add translator use backend translation
|
||||
if err = myTran.AddTranslator(content, file.Name()); err != nil {
|
||||
return nil, fmt.Errorf("add translator failed: %s %s", file.Name(), err)
|
||||
log.Debugf("add translator failed: %s %s", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
GlobalTrans = myTran.GlobalTrans
|
||||
|
||||
@@ -97,8 +97,29 @@ func getTran(lo locales.Translator) ut.Translator {
|
||||
return tran
|
||||
}
|
||||
|
||||
func NotBlank(fl validator.FieldLevel) (res bool) {
|
||||
field := fl.Field()
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
trimSpace := strings.TrimSpace(field.String())
|
||||
res := len(trimSpace) > 0
|
||||
if !res {
|
||||
field.SetString(trimSpace)
|
||||
}
|
||||
return true
|
||||
case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
|
||||
return field.Len() > 0
|
||||
case reflect.Ptr, reflect.Interface, reflect.Func:
|
||||
return !field.IsNil()
|
||||
default:
|
||||
return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
func createDefaultValidator(la i18n.Language) *validator.Validate {
|
||||
validate := validator.New()
|
||||
// _ = validate.RegisterValidation("notblank", validators.NotBlank)
|
||||
_ = validate.RegisterValidation("notblank", NotBlank)
|
||||
validate.RegisterTagNameFunc(func(fld reflect.StructField) (res string) {
|
||||
defer func() {
|
||||
if len(res) > 0 {
|
||||
|
||||
@@ -111,6 +111,7 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
|
||||
can, err := cc.rankService.CheckOperationPermission(ctx, req.UserID, permission.CommentEdit, req.CommentID)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
|
||||
@@ -139,6 +139,9 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
|
||||
}
|
||||
siteInfo := tc.SiteInfo(ctx)
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/questions", siteInfo.General.SiteUrl)
|
||||
if page > 1 {
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/questions?page=%d", siteInfo.General.SiteUrl, page)
|
||||
}
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
@@ -327,6 +330,9 @@ func (tc *TemplateController) TagList(ctx *gin.Context) {
|
||||
|
||||
siteInfo := tc.SiteInfo(ctx)
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/tags", siteInfo.General.SiteUrl)
|
||||
if req.Page > 1 {
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/tags?page=%d", siteInfo.General.SiteUrl, req.Page)
|
||||
}
|
||||
siteInfo.Title = fmt.Sprintf("%s - %s", "Tags", siteInfo.General.Name)
|
||||
tc.html(ctx, http.StatusOK, "tags.html", siteInfo, gin.H{
|
||||
"page": page,
|
||||
@@ -353,6 +359,9 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
|
||||
|
||||
siteInfo := tc.SiteInfo(ctx)
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/tags/%s", siteInfo.General.SiteUrl, tag)
|
||||
if req.Page > 1 {
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/tags/%s?page=%d", siteInfo.General.SiteUrl, tag, req.Page)
|
||||
}
|
||||
siteInfo.Description = htmltext.FetchExcerpt(taginifo.ParsedText, "...", 240)
|
||||
if len(taginifo.ParsedText) == 0 {
|
||||
siteInfo.Description = "The tag has no description."
|
||||
@@ -437,6 +446,7 @@ func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteI
|
||||
data["HeadCode"] = siteInfo.CustomCssHtml.CustomHead
|
||||
data["HeaderCode"] = siteInfo.CustomCssHtml.CustomHeader
|
||||
data["FooterCode"] = siteInfo.CustomCssHtml.CustomFooter
|
||||
data["Version"] = constant.Version
|
||||
_, ok := data["path"]
|
||||
if !ok {
|
||||
data["path"] = ""
|
||||
|
||||
@@ -157,8 +157,8 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
|
||||
code, err := uc.userService.RetrievePassWord(ctx, req)
|
||||
handler.HandleResponse(ctx, err, code)
|
||||
_, err := uc.userService.RetrievePassWord(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// UseRePassWord godoc
|
||||
|
||||
@@ -5,6 +5,10 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/pkg/checker"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
@@ -82,6 +86,18 @@ type InitBaseInfoReq struct {
|
||||
AdminEmail string `validate:"required,email,gt=0,lte=500" json:"email"`
|
||||
}
|
||||
|
||||
func (r *InitBaseInfoReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if checker.IsInvalidUsername(r.AdminName) {
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "name",
|
||||
ErrorMsg: reason.UsernameInvalid,
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *InitBaseInfoReq) FormatSiteUrl() {
|
||||
parsedUrl, err := url.Parse(r.SiteURL)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
|
||||
"github.com/answerdev/answer/internal/service/unique"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// collectionRepo collection repository
|
||||
@@ -29,15 +30,28 @@ func NewCollectionRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) collec
|
||||
|
||||
// AddCollection add collection
|
||||
func (cr *collectionRepo) AddCollection(ctx context.Context, collection *entity.Collection) (err error) {
|
||||
id, err := cr.uniqueIDRepo.GenUniqueIDStr(ctx, collection.TableName())
|
||||
if err == nil {
|
||||
collection.ID = id
|
||||
_, err = cr.data.DB.Insert(collection)
|
||||
_, err = cr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||
var has bool
|
||||
dbcollection := &entity.Collection{}
|
||||
result = nil
|
||||
has, err = session.Where("user_id = ? and object_id = ?", collection.UserID, collection.ObjectID).Get(dbcollection)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if has {
|
||||
return
|
||||
}
|
||||
id, err := cr.uniqueIDRepo.GenUniqueIDStr(ctx, collection.TableName())
|
||||
if err == nil {
|
||||
collection.ID = id
|
||||
_, err = session.Insert(collection)
|
||||
if err != nil {
|
||||
return result, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveCollection delete collection
|
||||
|
||||
@@ -33,7 +33,7 @@ var (
|
||||
"`user_id`",
|
||||
"`vote_count`",
|
||||
"`answer_count`",
|
||||
"0 as `accepted`",
|
||||
"CASE WHEN `accepted_answer_id` > 0 THEN 2 ELSE 0 END as `accepted`",
|
||||
"`question`.`status` as `status`",
|
||||
"`post_update_time`",
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ const (
|
||||
)
|
||||
|
||||
type AnswerAddReq struct {
|
||||
QuestionID string `json:"question_id" ` // question_id
|
||||
Content string `json:"content" ` // content
|
||||
HTML string `json:"html" ` // html
|
||||
UserID string `json:"-" ` // user_id
|
||||
QuestionID string `json:"question_id"`
|
||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
HTML string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
func (req *AnswerAddReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
@@ -32,13 +32,13 @@ func (req *AnswerAddReq) Check() (errFields []*validator.FormErrorField, err err
|
||||
}
|
||||
|
||||
type AnswerUpdateReq struct {
|
||||
ID string `json:"id"` // id
|
||||
QuestionID string `json:"question_id" ` // question_id
|
||||
UserID string `json:"-" ` // user_id
|
||||
Title string `json:"title" ` // title
|
||||
Content string `json:"content"` // content
|
||||
HTML string `json:"html" ` // html
|
||||
EditSummary string `validate:"omitempty" json:"edit_summary"` // edit_summary
|
||||
ID string `json:"id"`
|
||||
QuestionID string `json:"question_id"`
|
||||
Title string `json:"title"`
|
||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
EditSummary string `validate:"omitempty" json:"edit_summary"`
|
||||
HTML string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
NoNeedReview bool `json:"-"`
|
||||
// whether user can edit it
|
||||
CanEdit bool `json:"-"`
|
||||
|
||||
@@ -14,9 +14,9 @@ type AddCommentReq struct {
|
||||
// reply comment id
|
||||
ReplyCommentID string `validate:"omitempty" json:"reply_comment_id"`
|
||||
// original comment content
|
||||
OriginalText string `validate:"required" json:"original_text"`
|
||||
OriginalText string `validate:"required,notblank,gte=2,lte=600" json:"original_text"`
|
||||
// parsed comment content
|
||||
ParsedText string `validate:"required" json:"parsed_text"`
|
||||
ParsedText string `json:"-"`
|
||||
// @ user id list
|
||||
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
|
||||
// user id
|
||||
@@ -47,11 +47,12 @@ type UpdateCommentReq struct {
|
||||
// comment id
|
||||
CommentID string `validate:"required" json:"comment_id"`
|
||||
// original comment content
|
||||
OriginalText string `validate:"omitempty" json:"original_text"`
|
||||
OriginalText string `validate:"required,notblank,gte=2,lte=600" json:"original_text"`
|
||||
// parsed comment content
|
||||
ParsedText string `validate:"omitempty" json:"parsed_text"`
|
||||
ParsedText string `json:"-"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
UserID string `json:"-"`
|
||||
IsAdmin bool `json:"-"`
|
||||
}
|
||||
|
||||
func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
|
||||
@@ -41,11 +41,11 @@ type ReopenQuestionReq struct {
|
||||
|
||||
type QuestionAdd struct {
|
||||
// question title
|
||||
Title string `validate:"required,gte=6,lte=150" json:"title"`
|
||||
Title string `validate:"required,notblank,gte=6,lte=150" json:"title"`
|
||||
// content
|
||||
Content string `validate:"required,gte=6,lte=65535" json:"content"`
|
||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
// html
|
||||
HTML string `validate:"required,gte=6,lte=65535" json:"html"`
|
||||
HTML string `json:"-"`
|
||||
// tags
|
||||
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
||||
// user id
|
||||
@@ -90,11 +90,11 @@ type QuestionUpdate struct {
|
||||
// question id
|
||||
ID string `validate:"required" json:"id"`
|
||||
// question title
|
||||
Title string `validate:"required,gte=6,lte=150" json:"title"`
|
||||
Title string `validate:"required,notblank,gte=6,lte=150" json:"title"`
|
||||
// content
|
||||
Content string `validate:"required,gte=6,lte=65535" json:"content"`
|
||||
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
|
||||
// html
|
||||
HTML string `validate:"required,gte=6,lte=65535" json:"html"`
|
||||
HTML string `json:"-"`
|
||||
// tags
|
||||
Tags []*TagItem `validate:"required,dive" json:"tags"`
|
||||
// edit summary
|
||||
@@ -219,7 +219,7 @@ type UserQuestionInfo struct {
|
||||
ViewCount int `json:"view_count"`
|
||||
AnswerCount int `json:"answer_count"`
|
||||
CollectionCount int `json:"collection_count"`
|
||||
CreateTime int `json:"create_time"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
AcceptedAnswerID string `json:"accepted_answer_id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
@@ -253,6 +253,7 @@ const (
|
||||
|
||||
type QuestionPageResp struct {
|
||||
ID string `json:"id" `
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
Title string `json:"title"`
|
||||
UrlTitle string `json:"url_title"`
|
||||
Description string `json:"description"`
|
||||
|
||||
@@ -144,7 +144,7 @@ type TagItem struct {
|
||||
// original text
|
||||
OriginalText string `validate:"omitempty" json:"original_text"`
|
||||
// parsed text
|
||||
ParsedText string `validate:"omitempty" json:"parsed_text"`
|
||||
ParsedText string `json:"-"`
|
||||
}
|
||||
|
||||
// RemoveTagReq delete tag request
|
||||
@@ -166,7 +166,7 @@ type UpdateTagReq struct {
|
||||
// original text
|
||||
OriginalText string `validate:"omitempty" json:"original_text"`
|
||||
// parsed text
|
||||
ParsedText string `validate:"omitempty" json:"parsed_text"`
|
||||
ParsedText string `json:"-"`
|
||||
// edit summary
|
||||
EditSummary string `validate:"omitempty" json:"edit_summary"`
|
||||
// user id
|
||||
|
||||
@@ -2,12 +2,12 @@ package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/validator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/pkg/checker"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
@@ -283,7 +283,7 @@ type UpdateInfoRequest struct {
|
||||
// bio
|
||||
Bio string `validate:"omitempty,gt=0,lte=4096" json:"bio"`
|
||||
// bio
|
||||
BioHTML string `validate:"omitempty,gt=0,lte=4096" json:"bio_html"`
|
||||
BioHTML string `json:"-"`
|
||||
// website
|
||||
Website string `validate:"omitempty,gt=0,lte=500" json:"website"`
|
||||
// location
|
||||
@@ -298,12 +298,9 @@ type AvatarInfo struct {
|
||||
Custom string `validate:"omitempty,gt=0,lte=200" json:"custom"`
|
||||
}
|
||||
|
||||
func (u *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if len(u.Username) > 0 {
|
||||
errFields := make([]*validator.FormErrorField, 0)
|
||||
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
||||
match := re.MatchString(u.Username)
|
||||
if !match {
|
||||
func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if len(req.Username) > 0 {
|
||||
if checker.IsInvalidUsername(req.Username) {
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "username",
|
||||
ErrorMsg: reason.UsernameInvalid,
|
||||
@@ -312,6 +309,7 @@ func (u *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err
|
||||
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
||||
}
|
||||
}
|
||||
req.BioHTML = converter.Markdown2HTML(req.Bio)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -121,33 +121,14 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if objInfo.ObjectType == constant.QuestionObjectType {
|
||||
cs.notificationQuestionComment(ctx, objInfo.ObjectCreatorUserID,
|
||||
objInfo.QuestionID, objInfo.Title, comment.ID, req.UserID, comment.OriginalText)
|
||||
} else if objInfo.ObjectType == constant.AnswerObjectType {
|
||||
cs.notificationAnswerComment(ctx, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID,
|
||||
objInfo.ObjectCreatorUserID, comment.ID, req.UserID, comment.OriginalText)
|
||||
}
|
||||
if len(req.MentionUsernameList) > 0 {
|
||||
cs.notificationMention(ctx, req.MentionUsernameList, comment.ID, req.UserID)
|
||||
}
|
||||
|
||||
resp = &schema.GetCommentResp{}
|
||||
resp.SetFromComment(comment)
|
||||
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID, req.CanEdit, req.CanDelete)
|
||||
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID,
|
||||
time.Now(), req.CanEdit, req.CanDelete)
|
||||
|
||||
// get reply user info
|
||||
if len(resp.ReplyUserID) > 0 {
|
||||
replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
resp.ReplyUsername = replyUser.Username
|
||||
resp.ReplyUserDisplayName = replyUser.DisplayName
|
||||
resp.ReplyUserStatus = replyUser.Status
|
||||
}
|
||||
cs.notificationCommentReply(ctx, replyUser.ID, objInfo.QuestionID, req.UserID)
|
||||
commentResp, err := cs.addCommentNotification(ctx, req, resp, comment, objInfo)
|
||||
if err != nil {
|
||||
return commentResp, err
|
||||
}
|
||||
|
||||
// get user info
|
||||
@@ -178,6 +159,50 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) addCommentNotification(
|
||||
ctx context.Context, req *schema.AddCommentReq, resp *schema.GetCommentResp,
|
||||
comment *entity.Comment, objInfo *schema.SimpleObjectInfo) (*schema.GetCommentResp, error) {
|
||||
// The priority of the notification
|
||||
// 1. reply to user
|
||||
// 2. comment mention to user
|
||||
// 3. answer or question was commented
|
||||
alreadyNotifiedUserID := make(map[string]bool)
|
||||
|
||||
// get reply user info
|
||||
if len(resp.ReplyUserID) > 0 && resp.ReplyUserID != req.UserID {
|
||||
replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
resp.ReplyUsername = replyUser.Username
|
||||
resp.ReplyUserDisplayName = replyUser.DisplayName
|
||||
resp.ReplyUserStatus = replyUser.Status
|
||||
}
|
||||
cs.notificationCommentReply(ctx, replyUser.ID, comment.ID, req.UserID)
|
||||
alreadyNotifiedUserID[replyUser.ID] = true
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(req.MentionUsernameList) > 0 {
|
||||
alreadyNotifiedUserIDs := cs.notificationMention(
|
||||
ctx, req.MentionUsernameList, comment.ID, req.UserID, alreadyNotifiedUserID)
|
||||
for _, userID := range alreadyNotifiedUserIDs {
|
||||
alreadyNotifiedUserID[userID] = true
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if objInfo.ObjectType == constant.QuestionObjectType && !alreadyNotifiedUserID[objInfo.ObjectCreatorUserID] {
|
||||
cs.notificationQuestionComment(ctx, objInfo.ObjectCreatorUserID,
|
||||
objInfo.QuestionID, objInfo.Title, comment.ID, req.UserID, comment.OriginalText)
|
||||
} else if objInfo.ObjectType == constant.AnswerObjectType && !alreadyNotifiedUserID[objInfo.ObjectCreatorUserID] {
|
||||
cs.notificationAnswerComment(ctx, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID,
|
||||
objInfo.ObjectCreatorUserID, comment.ID, req.UserID, comment.OriginalText)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// RemoveComment delete comment
|
||||
func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveCommentReq) (err error) {
|
||||
return cs.commentRepo.RemoveComment(ctx, req.CommentID)
|
||||
@@ -185,6 +210,19 @@ func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveC
|
||||
|
||||
// UpdateComment update comment
|
||||
func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (err error) {
|
||||
old, exist, err := cs.commentCommonRepo.GetComment(ctx, req.CommentID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
return errors.BadRequest(reason.CommentNotFound)
|
||||
}
|
||||
|
||||
// user can edit the comment that was posted by himself before deadline.
|
||||
if !req.IsAdmin && (time.Now().After(old.CreatedAt.Add(constant.CommentEditDeadline))) {
|
||||
return errors.BadRequest(reason.CommentCannotEditAfterDeadline)
|
||||
}
|
||||
|
||||
comment := &entity.Comment{}
|
||||
_ = copier.Copy(comment, req)
|
||||
comment.ID = req.CommentID
|
||||
@@ -198,7 +236,7 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
return nil, errors.BadRequest(reason.UnknownError)
|
||||
return nil, errors.BadRequest(reason.CommentNotFound)
|
||||
}
|
||||
|
||||
resp = &schema.GetCommentResp{
|
||||
@@ -243,7 +281,8 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
|
||||
// check if current user vote this comment
|
||||
resp.IsVote = cs.checkIsVote(ctx, req.UserID, resp.CommentID)
|
||||
|
||||
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID, req.CanEdit, req.CanDelete)
|
||||
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID,
|
||||
comment.CreatedAt, req.CanEdit, req.CanDelete)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -339,7 +378,7 @@ func (cs *CommentService) convertCommentEntity2Resp(ctx context.Context, req *sc
|
||||
commentResp.IsVote = cs.checkIsVote(ctx, req.UserID, commentResp.CommentID)
|
||||
|
||||
commentResp.MemberActions = permission.GetCommentPermission(ctx,
|
||||
req.UserID, commentResp.UserID, req.CanEdit, req.CanDelete)
|
||||
req.UserID, commentResp.UserID, comment.CreatedAt, req.CanEdit, req.CanDelete)
|
||||
return commentResp, nil
|
||||
}
|
||||
|
||||
@@ -521,14 +560,16 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse
|
||||
notice_queue.AddNotification(msg)
|
||||
}
|
||||
|
||||
func (cs *CommentService) notificationMention(ctx context.Context, mentionUsernameList []string, commentID, commentUserID string) {
|
||||
func (cs *CommentService) notificationMention(
|
||||
ctx context.Context, mentionUsernameList []string, commentID, commentUserID string,
|
||||
alreadyNotifiedUserID map[string]bool) (alreadyNotifiedUserIDs []string) {
|
||||
for _, username := range mentionUsernameList {
|
||||
userInfo, exist, err := cs.userCommon.GetUserBasicInfoByUserName(ctx, username)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
if exist {
|
||||
if exist && !alreadyNotifiedUserID[userInfo.ID] {
|
||||
msg := &schema.NotificationMsg{
|
||||
ReceiverUserID: userInfo.ID,
|
||||
TriggerUserID: commentUserID,
|
||||
@@ -538,6 +579,8 @@ func (cs *CommentService) notificationMention(ctx context.Context, mentionUserna
|
||||
msg.ObjectType = constant.CommentObjectType
|
||||
msg.NotificationAction = constant.MentionYou
|
||||
notice_queue.AddNotification(msg)
|
||||
alreadyNotifiedUserIDs = append(alreadyNotifiedUserIDs, userInfo.ID)
|
||||
}
|
||||
}
|
||||
return alreadyNotifiedUserIDs
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ package permission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
)
|
||||
|
||||
// GetCommentPermission get comment permission
|
||||
func GetCommentPermission(ctx context.Context, userID string, creatorUserID string, canEdit, canDelete bool) (
|
||||
actions []*schema.PermissionMemberAction) {
|
||||
func GetCommentPermission(ctx context.Context, userID string, creatorUserID string,
|
||||
createdAt time.Time, canEdit, canDelete bool) (actions []*schema.PermissionMemberAction) {
|
||||
actions = make([]*schema.PermissionMemberAction, 0)
|
||||
if len(userID) > 0 {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
@@ -17,7 +19,8 @@ func GetCommentPermission(ctx context.Context, userID string, creatorUserID stri
|
||||
Type: "reason",
|
||||
})
|
||||
}
|
||||
if canEdit || userID == creatorUserID {
|
||||
deadline := createdAt.Add(constant.CommentEditDeadline)
|
||||
if canEdit || (userID == creatorUserID && time.Now().Before(deadline)) {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "edit",
|
||||
Name: "Edit",
|
||||
|
||||
@@ -254,6 +254,7 @@ func (qs *QuestionCommon) FormatQuestionsPage(
|
||||
for _, questionInfo := range questionList {
|
||||
t := &schema.QuestionPageResp{
|
||||
ID: questionInfo.ID,
|
||||
CreatedAt: questionInfo.CreatedAt.Unix(),
|
||||
Title: questionInfo.Title,
|
||||
UrlTitle: htmltext.UrlTitle(questionInfo.Title),
|
||||
Description: htmltext.FetchExcerpt(questionInfo.ParsedText, "...", 240),
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/service/service_config"
|
||||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
"github.com/answerdev/answer/pkg/checker"
|
||||
"github.com/answerdev/answer/pkg/dir"
|
||||
"github.com/answerdev/answer/pkg/uid"
|
||||
"github.com/disintegration/imaging"
|
||||
@@ -40,10 +40,10 @@ var (
|
||||
".jpg": imaging.JPEG,
|
||||
".jpeg": imaging.JPEG,
|
||||
".png": imaging.PNG,
|
||||
".gif": imaging.GIF,
|
||||
".tif": imaging.TIFF,
|
||||
".tiff": imaging.TIFF,
|
||||
".bmp": imaging.BMP,
|
||||
//".gif": imaging.GIF,
|
||||
//".tif": imaging.TIFF,
|
||||
//".tiff": imaging.TIFF,
|
||||
//".bmp": imaging.BMP,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -74,13 +74,11 @@ func (us *UploaderService) UploadAvatarFile(ctx *gin.Context) (url string, err e
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 5*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
@@ -147,13 +145,11 @@ func (us *UploaderService) UploadPostFile(ctx *gin.Context) (
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
if _, ok := FormatExts[fileExt]; !ok {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
@@ -167,14 +163,12 @@ func (us *UploaderService) UploadBrandingFile(ctx *gin.Context) (
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||
_, file, err := ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
||||
}
|
||||
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||
_, ok := FormatExts[fileExt]
|
||||
if !ok && fileExt != ".ico" {
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||
return
|
||||
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
|
||||
}
|
||||
|
||||
newFilename := fmt.Sprintf("%s%s", uid.IDStr12(), fileExt)
|
||||
@@ -192,6 +186,17 @@ func (us *UploaderService) uploadFile(ctx *gin.Context, file *multipart.FileHead
|
||||
if err := ctx.SaveUploadedFile(file, filePath); err != nil {
|
||||
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if !checker.IsSupportedImageFile(src, filepath.Ext(fileSubPath)) {
|
||||
return "", errors.BadRequest(reason.UploadFileUnsupportedFileFormat)
|
||||
}
|
||||
|
||||
url = fmt.Sprintf("%s/uploads/%s", siteGeneral.SiteUrl, fileSubPath)
|
||||
return url, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Chain-Zhang/pinyin"
|
||||
@@ -120,9 +119,7 @@ func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (use
|
||||
username = strings.ToLower(username)
|
||||
suffix := ""
|
||||
|
||||
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
||||
match := re.MatchString(username)
|
||||
if !match {
|
||||
if checker.IsInvalidUsername(username) {
|
||||
return "", errors.BadRequest(reason.UsernameInvalid)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package checker
|
||||
|
||||
import (
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsSupportedImageFile currently answers support image type is `image/jpeg,image/jpg,image/png`
|
||||
func IsSupportedImageFile(file io.Reader, ext string) bool {
|
||||
ext = strings.TrimPrefix(ext, ".")
|
||||
var err error
|
||||
switch strings.ToUpper(ext) {
|
||||
case "JPG", "JPEG":
|
||||
_, err = jpeg.Decode(file)
|
||||
case "PNG":
|
||||
_, err = png.Decode(file)
|
||||
case "ICO":
|
||||
// TODO: There is currently no good Golang library to parse whether the image is in ico format.
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package checker
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
usernameReg = regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
||||
)
|
||||
|
||||
func IsInvalidUsername(username string) bool {
|
||||
return !usernameReg.MatchString(username)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package converter
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"github.com/yuin/goldmark"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
goldmarkHTML "github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
@@ -54,6 +56,9 @@ type DangerousHTMLRenderer struct {
|
||||
func (r *DangerousHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
|
||||
reg.Register(ast.KindRawHTML, r.renderRawHTML)
|
||||
reg.Register(ast.KindLink, r.renderLink)
|
||||
reg.Register(ast.KindAutoLink, r.renderAutoLink)
|
||||
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
@@ -85,3 +90,56 @@ func (r *DangerousHTMLRenderer) renderHTMLBlock(w util.BufWriter, source []byte,
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
|
||||
n := node.(*ast.Link)
|
||||
if entering && r.renderLinkIsUrl(string(n.Destination)) {
|
||||
_, _ = w.WriteString("<a href=\"")
|
||||
if r.Unsafe || !html.IsDangerousURL(n.Destination) {
|
||||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||
}
|
||||
_ = w.WriteByte('"')
|
||||
if n.Title != nil {
|
||||
_, _ = w.WriteString(` title="`)
|
||||
r.Writer.Write(w, n.Title)
|
||||
_ = w.WriteByte('"')
|
||||
}
|
||||
if n.Attributes() != nil {
|
||||
html.RenderAttributes(w, n, html.LinkAttributeFilter)
|
||||
}
|
||||
_ = w.WriteByte('>')
|
||||
} else {
|
||||
_, _ = w.WriteString("</a>")
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
n := node.(*ast.AutoLink)
|
||||
|
||||
if !entering || !r.renderLinkIsUrl(string(n.URL(source))) {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
_, _ = w.WriteString(`<a href="`)
|
||||
url := n.URL(source)
|
||||
label := n.Label(source)
|
||||
if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
|
||||
_, _ = w.WriteString("mailto:")
|
||||
}
|
||||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
|
||||
if n.Attributes() != nil {
|
||||
_ = w.WriteByte('"')
|
||||
html.RenderAttributes(w, n, html.LinkAttributeFilter)
|
||||
_ = w.WriteByte('>')
|
||||
} else {
|
||||
_, _ = w.WriteString(`">`)
|
||||
}
|
||||
_, _ = w.Write(util.EscapeHTML(label))
|
||||
_, _ = w.WriteString(`</a>`)
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *DangerousHTMLRenderer) renderLinkIsUrl(verifyUrl string) bool {
|
||||
return govalidator.IsURL(verifyUrl)
|
||||
}
|
||||
|
||||
+5
-1
@@ -9,7 +9,11 @@
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
/build/*/*/*
|
||||
/build/*.json
|
||||
/build/*.html
|
||||
/build/*.txt
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -1 +0,0 @@
|
||||
<!doctype html><html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><script defer="defer" src="/static/js/main.cb9bf782.js"></script><link href="/static/css/main.b8d8739f.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="spin-mask"><noscript><style>#spin-mask{display:none!important}</style></noscript><style>@keyframes _doc-spin{to{transform:rotate(360deg)}}#spin-mask{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#fff;z-index:9999}#spin-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#spin-container .spinner{box-sizing:border-box;display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25rem solid currentColor;border-right-color:transparent;color:rgba(108,117,125,.75);border-radius:50%;animation:.75s linear infinite _doc-spin}</style><div id="spin-container"><div class="spinner"></div></div></div></div></body></html>
|
||||
@@ -2,7 +2,6 @@ import React, { FormEvent, useState, useEffect } from 'react';
|
||||
import { Form, Button, Stack, ButtonGroup } from 'react-bootstrap';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { marked } from 'marked';
|
||||
import MD5 from 'md5';
|
||||
|
||||
import type { FormDataType } from '@/common/interface';
|
||||
@@ -205,7 +204,6 @@ const Index: React.FC = () => {
|
||||
bio: formData.bio.value,
|
||||
website: formData.website.value,
|
||||
location: formData.location.value,
|
||||
bio_html: marked.parse(formData.bio.value),
|
||||
};
|
||||
|
||||
modifyUserInfo(params)
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>{{.title}}</title>
|
||||
<meta name="description" content="{{.description}}" data-rh="true" />
|
||||
{{if .keywords }}
|
||||
<meta name="keywords" content="{{.keywords}}" data-rh="true" />
|
||||
{{end}}
|
||||
<meta name="generator" content="Answer {{.Version}} - https://github.com/answerdev/answer">
|
||||
{{if .keywords }}<meta name="keywords" content="{{.keywords}}" data-rh="true" />{{end}}
|
||||
|
||||
<link rel="canonical" href="{{.siteinfo.Canonical}}" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link href="{{.cssPath}}" rel="stylesheet" />
|
||||
|
||||
Reference in New Issue
Block a user