144 Commits

Author SHA1 Message Date
ferhat elmas da622a4927 test(converter): pin renderLinkIsUrl behavior
Add a table-driven test covering the markdown link destination check
before replacing the govalidator dependency with stdlib logic.

Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
2026-08-11 19:34:58 +08:00
Ahmed Qasid e884bb61cb fix: avoid topic fallback for non-Latin titles via pragmatic ASCII transliteration (#1526)
# fix: avoid `topic` fallback for non-Latin titles via pragmatic ASCII
transliteration

> **Scope update (in response to review):** this PR is intentionally
broader than its original "Arabic-only" framing. The implementation
changes URL slug generation for **every non-Latin, non-CJK script** that
`slugify` previously stripped — see *Scope* below for the explicit list.
The goal is *not* linguistically correct romanization; it is "avoid
collapsing to `/topic` by producing a usable ASCII slug."

## What this PR is (and isn't)

**Goal:** when a question title contains characters outside Basic Latin
/ Latin Extended / CJK Han, generate a URL slug that is a deterministic
ASCII approximation instead of letting `slugify` strip everything and
falling back to the literal `"topic"`.

**Non-goal:** this is *not* a linguistically correct multi-language
romanizer. The output is a machine-acceptable ASCII slug, not what a
native speaker would choose. For example, `こんにちは` → `konnichiha` (not
the more natural `kon'nichiwa`), `ไทย` → `aithy` (not `thai`). Treat the
slug as an opaque, stable, indexable identifier — the
path-after-`/questions/<id>/` is for SEO and shareability, the canonical
reference is always the ID.

## The bug

Pure non-Latin titles previously got stripped by `slugify.Slugify`, hit
the empty-result fallback in `htmltext.UrlTitle`, and collapsed to the
literal slug `"topic"`. On a live multilingual site, every Arabic / Thai
/ Japanese-hiragana / Korean / Hebrew / Cyrillic question ended up at
`/questions/<id>/topic`.

## The fix

`UrlTitle()` gets a `convertNonLatin` pre-step that mirrors the existing
`convertChinese` pre-step pattern, using
`github.com/mozillazg/go-unidecode` (same author as `go-pinyin` already
in the repo, to minimise new-dep friction).

```
UrlTitle(title)
  → convertChinese(title)        // pre-existing: Han-block → pinyin
  → convertNonLatin(title)       // NEW: detect non-Latin letters → unidecode to ASCII
  → clearEmoji / slugify / url.QueryEscape / cutLongTitle (unchanged)
```

The non-Latin detector skips ASCII, Latin-1 Supplement, Latin
Extended-A/B, and CJK Han. Inputs that hit none of those non-Latin
letter categories short-circuit and return unchanged, so Latin-only and
Chinese-only inputs remain byte-identical (pinned by tests).

## Scope — what scripts are affected

This PR changes behavior for **any** title containing letters in scripts
that `slugify` doesn't handle. Confirmed by tests in
`pkg/htmltext/htmltext_test.go`:

| Script | Example title | Before | After |
| --- | --- | --- | --- |
| Arabic | `كيف حالك` | `topic` | `kyf-hlk` |
| Mixed Latin + Arabic | `مرحبا hello` | `hello` | `mrhb-hello` |
| Thai | `ไทย ไทย` | `topic` | `aithy-aithy` |
| Japanese hiragana | `こんにちは` | `topic` | `konnichiha` |
| Korean | `안녕하세요` | `topic` | `annyeonghaseyo` |
| Hebrew | `שלום עולם` | `topic` | `shlvm-vlm` |
| Cyrillic | `Привет мир` | `topic` | `privet-mir` |

**Unchanged:**

| Case | Behavior |
| --- | --- |
| Pure Latin (`hello world`) | unchanged → `hello-world` |
| Pure Chinese (`这是一个,标题,title`) | unchanged → `zhe-shi-yi-ge-biao-ti`
(pinyin path) |
| Japanese with Han-block kanji (`日本`) | unchanged → `ri-ben` (caught by
pre-existing pinyin path; treated as Chinese reading, not Japanese — a
pre-existing limitation, **not** introduced by this PR) |
| Emoji only (`😂😂😂`) | unchanged → `topic` |
| Empty / whitespace | unchanged → `topic` |

## Transliteration quality — explicit acknowledgement

`go-unidecode` is a generic Unicode → ASCII approximation. It is **not**
a per-language romanization library. Specifically:

- It will pick *one* approximation per codepoint regardless of language
context. `ใ` → `ai` (Thai romanization is `i` or `ai` depending on
standard), `한` → `han`, `語` → `Yu` (Chinese pinyin reading even when
used in Japanese), etc.
- The result is *good enough* to be a stable, URL-safe,
human-recognizable handle, but speakers of the source language will not
consider it "correct."
- It is deterministic, so the same title always produces the same slug —
important since `url_title` is recomputed on every request.

If maintainers prefer to scope this PR more narrowly (e.g. Arabic only,
and reject Thai/Hebrew/Cyrillic/etc.), the detector in
`containsNonLatin` can be tightened to specific Unicode blocks — but
that means the other scripts continue to collapse to `topic`, which is
the bug we're trying to fix. I'd argue the broader fix is preferable to
a piecemeal one, but happy to narrow if you want.

## Live deployment / real-world verification

This patch has been running in production on
**[ask.namasoft.com](https://ask.namasoft.com)** (an Apache Answer
instance we operate) since deployment, built directly from this branch
via `docker compose build`. The site hosts Arabic-language questions, so
the fix exercises the affected code path on every page load.

Sample question URL on the deployed instance:

> `https://ask.namasoft.com/questions/10010000000000115`

The slug in the URL is the transliterated Arabic title rather than
`topic`. No data migration was needed since `url_title` is computed on
every request from `Title` and never persisted (see *Why this is safe to
ship* below).

## Admin-configurable

The transliteration is gated by a package-level `atomic.Bool` (default
**on**, since the current behavior is objectively broken for affected
users):

- `htmltext.SetTransliterateNonLatin(enabled bool)`
- `htmltext.IsTransliterateNonLatinEnabled() bool`

This is deliberately the minimum surface needed to satisfy "the setting
must be readable from `UrlTitle()`". A follow-up PR can add an admin UI
section that calls `SetTransliterateNonLatin` on save and on startup,
without having to re-plumb every `htmltext.UrlTitle` call site through
`context.Context`.

**Default choice — please confirm:** I picked **default-on** because the
existing `topic` behavior is a bug for affected users. If you'd prefer
default-off for strict backward compat on existing installs, flip the
`init()` in `pkg/htmltext/htmltext.go` to `Store(false)` and surface the
toggle as opt-in.

## Why this is safe to ship

- `url_title` is **not** a persisted column. It's not on the `Question`
entity in `internal/entity/question_entity.go`, no migration has ever
added/dropped it, and every call site (`question_service.go`,
`revision_service.go`, `vote_service.go`,
search/report/review/rank/comment services, controllers, repos)
recomputes it from `Title` at response-build time via
`htmltext.UrlTitle(...)`.
- That means the fix is read-only: existing rows light up with correct
slugs on the next request, with no migration and no data rewrite.
- Rollback is just redeploying the prior image; nothing on disk changes.

## Test coverage

`pkg/htmltext/htmltext_test.go`:

- **`TestUrlTitleTable`** — table-driven, one case per affected script
(the full matrix above), plus:
  - `empty` → `topic`
  - `pure latin unchanged` → byte-identical to pre-fix
- `pure chinese unchanged` → byte-identical to pre-fix (pins existing
pinyin behavior)
- `japanese kanji goes through pinyin path unchanged` → documents the
pre-existing Han-block limitation
  - `emoji only falls back to topic` → unchanged
- `long arabic truncates at cutLongTitle boundary` → exercises the
150-byte cap and UTF-8 boundary safety
- **`TestUrlTitleTransliterationToggle`** — with the toggle off,
non-Latin titles collapse to `topic` (pre-fix behavior); with it on,
they transliterate.
- Existing `TestUrlTitle` left untouched.

Test plan for reviewers:

- [ ] `go test ./pkg/htmltext/...` — all pass
- [ ] Visit the live sample URL above and confirm slug is
transliterated, not `topic`
- [ ] Verify Chinese / Latin / emoji-only / empty behavior is
byte-identical to `main` (covered by table tests)

## Out of scope (intentionally)

- No admin UI / site setting plumbing in this PR — see
*Admin-configurable* above. Happy to do the React `Non-Latin Languages
Handling` admin page + `SiteType` + service / controller / migration in
a follow-up if maintainers want it.
- No change to the `"topic"` empty-result fallback.
- No plugin interface for slug generation — mirrored the existing
`convertChinese` pre-step pattern instead.
- No per-language romanization library — this is an explicit non-goal;
see *Transliteration quality* above.

## Issues / discussion

I didn't find an existing upstream issue covering this — happy to be
pointed at one if there is.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: LinkinStars <linkinstar@foxmail.com>
2026-06-03 22:06:26 +08:00
LinkinStars b1da65fbe4 Merge remote-tracking branch 'origin/fix/2.0.1/chat' into test 2026-05-11 12:16:58 +08:00
LinkinStars cfc3e54f30 fix(image): enhance image decoding by implementing format-specific checks for JPEG, PNG, and GIF 2026-03-25 19:31:31 +08:00
LinkinStars 52d057ef18 fix: add ID validation and normalization functions for comment handling 2026-02-09 20:27:37 +08:00
LinkinStars 638fb082c5 fix: enhance bracket handling in formatting and add concurrency test for internationalization 2026-02-05 16:17:32 +08:00
ferhat elmas 670aa32325 refactor(lint): add new linters and fix their issues
* gocritic
* misspell
* modernize (aside, bumping go would be nice)
* testifylint
* unconvert
* unparam
* whitespace

related to #1432

Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
2025-12-05 10:45:55 +08:00
LinkinStars 5e705a124b refactor(lint): improve error handling and code consistency across multiple files 2025-12-01 12:27:25 +08:00
LinkinStars 9540ef6005 refactor(goimports): add goimports to golangci-lint configuration #1432 2025-12-01 11:32:12 +08:00
ferhat elmas f723d120d9 feat: add golangci-lint into lint target
* replace empty interface with any
* run fmt via golangci-lint

related to #1432

Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
2025-12-01 11:14:38 +08:00
ferhat elmas ce053ccfa6 fix: multi byte run boundary for cut long title
Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
2025-11-25 17:58:11 +08:00
ferhat elmas a15dd41550 refactor(internal): compile regex once while clearing text
Regex and Replacer can be reused.
No need to recreate for each invocation.

Signed-off-by: ferhat elmas <elmas.ferhat@gmail.com>
2025-11-24 10:32:47 +08:00
Sonui 93e183be62 feat: Add resetPassword cli tool 2025-10-28 14:52:53 +08:00
Luffy bec3e355a9 feat: Add user comment moderation 2025-09-23 14:57:07 +08:00
shuai 0168309535 fix: resolve conflict 2025-03-17 10:53:33 +08:00
Luffy cd24c91fb1 fix: update admin name validation and username regex 2025-03-17 10:37:00 +08:00
LinkinStars 849ad8dfc5 Merge remote-tracking branch 'origin/dev' into test 2025-03-14 14:17:00 +08:00
Luffy ed2a5ba8f6 feat: add footnote extension to markdown converter 2025-03-14 11:10:52 +08:00
LinkinStars 3811b43fa3 Merge branch 'dev' into test 2025-01-16 15:56:05 +08:00
Young Xu 581f73c857 chore: instance rand.Seed to rand.Source (#1233)
The `rand.Seed` method is currently deprecated, use rand Replace with
the `NewSource` method.

```
// Deprecated: As of Go 1.20 there is no reason to call Seed with
// a random value. Programs that call Seed with a known value to get
// a specific sequence of results should use New(NewSource(seed)) to
// obtain a local random generator.
```

---------

Signed-off-by: Young Xu <xuthus5@gmail.com>
Co-authored-by: LinkinStars <linkinstar@foxmail.com>
2025-01-16 11:16:57 +08:00
LinkinStars 5cffab8649 feat(service): implement file cleanup and deletion functionality 2025-01-10 10:43:35 +08:00
LinkinStars 47c666200e Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	internal/base/middleware/accept_language.go
#	internal/schema/answer_schema.go
#	internal/schema/comment_schema.go
#	internal/service/user_admin/user_backyard.go
2025-01-07 12:48:13 +08:00
LinkinStars 0618947350 refactor(name): rename incubator-answer 2025-01-07 10:39:23 +08:00
LinkinStars 57b153c9a9 feat(user): add FilterEmptyString function and apply to user ID filtering 2024-12-25 16:00:08 +08:00
Luffy 6203b5b441 fix: use TrimSpace 2024-12-11 17:11:38 +08:00
Luffy b31d644b92 fix: Fix render comments with line breaks 2024-12-11 17:11:38 +08:00
wxt 73eb2a1133 test: fix TestGetAvatarURL 2024-12-09 11:52:18 +08:00
LinkinStars a955ba0658 feat(upload): add support for file attachments and enhance image upload 2024-12-09 11:52:18 +08:00
sy-records ad956d686e fix: Markdown parsing without incrementing ordered list numbers 2024-12-09 11:52:18 +08:00
LinkinStars f29b43b956 feat(file): update the max image size 2024-12-09 11:52:18 +08:00
Sonui 62b9104a68 fix: support short id 2024-10-12 10:17:18 +08:00
Sonui f575aa9b5f docs: add Apache License headers to source files 2024-10-12 10:17:18 +08:00
Sonui 3eb9822c09 feat(question): support linking question 2024-10-12 10:17:18 +08:00
zahash 3e91f03b40 change minimum username length to 2 instead of 4 2024-10-12 09:59:17 +08:00
LinkinStars e062cbcb0e refactor(gomod): replace pinyin dependency 2024-09-26 16:25:00 +08:00
LinkinStars 0d6ea89ad3 refactor(gomod): upgrade golang and dependence version 2024-09-23 15:00:34 +08:00
LinkinStars f014602ec7 feat(user): change the hash way of gravatar 2024-07-22 11:40:04 +08:00
sy-records 41691eed2f fix: When @ someone, markdown render error 2024-06-27 14:53:04 +08:00
kumfo f972a23dd8 feat(plugin/embed): merge from dev and add embed plugin 2024-05-23 15:57:10 +08:00
LinkinStars e53c7b3c8a docs(asf): add ASF header 2024-03-22 17:30:48 +08:00
LinkinStars abe0680641 Release/1.3.0 (#876)
Co-authored-by: shuai <lishuailing@sifou.com>
Co-authored-by: Han Gao <dhangao@hotmail.com>
Co-authored-by: wzy <770954908@qq.com>
Co-authored-by: zahash <zahash.z@gmail.com>
Co-authored-by: Hosein Beigi <hbsciw@gmail.com>
Co-authored-by: kumfo <kumfo@sifou.com>
Co-authored-by: sy-records <52o@qq52o.cn>
Co-authored-by: 而立 <cuihengqing@cai-inc.com>
Co-authored-by: foxzero-007 <chq2549603631@gmail.com>
Co-authored-by: easy <58644520+foxzero-007@users.noreply.github.com>
Co-authored-by: robin <robin@rnode.me>
Co-authored-by: Akhil <surapuramakhil@gmail.com>
Co-authored-by: Shaobiao Lin <whalevocal@gmail.com>
2024-03-22 17:25:46 +08:00
LinkinStars 0849e8f540 refactor(file): refactor file format check function 2024-01-30 16:36:39 +08:00
LinkinStars 968f23db7f Release/1.2.5 (#761)
Build Binary For Release / build-goreleaser (push) Has been cancelled
Build Docker Image For Release / build (push) Has been cancelled
Signed-off-by: Adam Vollrath <adam.d.vollrath@gmail.com>
Co-authored-by: shuai <lishuailing@sifou.com>
Co-authored-by: kelvinkuo <kelvinkuo224@gmail.com>
Co-authored-by: hgaol <dhangao@hotmail.com>
Co-authored-by: sy-records <52o@qq52o.cn>
Co-authored-by: Adam Vollrath <adam.d.vollrath@gmail.com>
Co-authored-by: kumfo <kumfo@sifou.com>
Co-authored-by: Yang Wong <yang wang>
Co-authored-by: hbsciw <hbsciw@gmail.com>
2024-01-29 19:14:13 +08:00
LinkinStars 937de43978 Release/1.2.5 (#759)
Signed-off-by: Adam Vollrath <adam.d.vollrath@gmail.com>
Co-authored-by: shuai <lishuailing@sifou.com>
Co-authored-by: kelvinkuo <kelvinkuo224@gmail.com>
Co-authored-by: hgaol <dhangao@hotmail.com>
Co-authored-by: sy-records <52o@qq52o.cn>
Co-authored-by: Adam Vollrath <adam.d.vollrath@gmail.com>
Co-authored-by: kumfo <kumfo@sifou.com>
Co-authored-by: Yang Wong <yang wang>
Co-authored-by: hbsciw <hbsciw@gmail.com>
2024-01-29 17:19:41 +08:00
LinkinStars a458f3409c rename all old answerdev/answer link to apache/incubator-answer (#603)
Co-authored-by: shuai <lishuailing@sifou.com>
2023-11-01 14:48:35 +08:00
tison 3d2187d849 chore: add license headers
Signed-off-by: tison <wander4096@gmail.com>
2023-10-26 11:11:23 +08:00
LinkinStars ee5613099f fix(email): fix all incorrect url in email. 2023-10-08 17:58:52 +08:00
LinkinStars 3242eba69c Merge remote-tracking branch 'origin/feat/1.2.0/img' into feat/1.2.0/question 2023-09-22 14:44:49 +08:00
LinkinStars b2d5781633 feat(user): support upload webp image 2023-09-22 14:36:40 +08:00
LinkinStars da0bc8b76d feat(user): change delete user display info 2023-09-22 11:47:05 +08:00