@wix/himalaya-cli 0.1599.0 → 0.1600.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1351 -977
- package/guides/himi-json-output.md +25 -1
- package/guides/one-app-to-himi-app.md +33 -11
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +182 -0
- package/templates/beauty-salon/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/beauty-salon/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/beauty-salon/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/branded-lite/dev/index.ts +182 -0
- package/templates/branded-lite/l10n/en.json +10 -7
- package/templates/branded-lite/tier5-src/_shared/header-badges.ts +113 -0
- package/templates/branded-lite/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/branded-lite/tier5-src/_shared/source-badges.ts +70 -0
- package/templates/branded-lite/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/branded-lite/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-header-badges/index.ts +38 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/community-club/dev/index.ts +182 -0
- package/templates/community-club/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/community-club/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/community-club/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/community-club/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/events-meetups/dev/index.ts +182 -0
- package/templates/events-meetups/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/events-meetups/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/events-meetups/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/gym-fitness/dev/index.ts +182 -0
- package/templates/gym-fitness/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/gym-fitness/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/gym-fitness/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/nonprofit-cause/dev/index.ts +182 -0
- package/templates/nonprofit-cause/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/nonprofit-cause/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/nonprofit-cause/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/podcast-creator/dev/index.ts +182 -0
- package/templates/podcast-creator/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/podcast-creator/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/podcast-creator/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/portfolio-creative/dev/index.ts +182 -0
- package/templates/portfolio-creative/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/portfolio-creative/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/portfolio-creative/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/restaurant/dev/index.ts +182 -0
- package/templates/restaurant/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/restaurant/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/restaurant/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/restaurant/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/retail-store/dev/index.ts +182 -0
- package/templates/retail-store/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/retail-store/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/retail-store/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/retail-store/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +5 -2
- package/templates/yoga-studio/dev/index.ts +182 -0
- package/templates/yoga-studio/tier5-src/_shared/himi-blog-hidden.ts +66 -0
- package/templates/yoga-studio/tier5-src/himi-blog-feed/index.ts +3 -1
- package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +42 -0
- package/templates/yoga-studio/tier5-src/himi-blog-saved/index.ts +4 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +5 -2
|
@@ -406,7 +406,31 @@ unresolved migration finding.
|
|
|
406
406
|
- `config_uninspectable`: the target `config.ts` cannot be read or the relevant
|
|
407
407
|
`navigation.topBarDefault.actions` cannot be inspected (exit 1).
|
|
408
408
|
- `topbar_coverage_failed`: a source top-bar control has no matching action
|
|
409
|
-
with a non-empty `onTap.actionId
|
|
409
|
+
with a non-empty `onTap.actionId`, or a source badge's action is not a complete
|
|
410
|
+
live count. When the app's `navLogicBundle` is a Branded Lite badge worker, the
|
|
411
|
+
badge's `valuePath` must also be a count that worker holds
|
|
412
|
+
(`$service:brandedLiteNav.chatBadge`, `.notificationsBadge`, `.cartBadge`) and sit on
|
|
413
|
+
the matching button (exit 1).
|
|
414
|
+
|
|
415
|
+
## `himi one-app header-badges` codes
|
|
416
|
+
|
|
417
|
+
Binds the source header's bell, chat and cart counts in `config.ts`. Success prints
|
|
418
|
+
`ok: true` and a `status`: `written`, `unchanged`, `would_write` (`--dry-run`),
|
|
419
|
+
`no_badges` or `nothing_to_bind`; `unmapped` lists badge buttons that are not clearly
|
|
420
|
+
a bell, chat or cart button and are never guessed. Failures print `ok: false` and a
|
|
421
|
+
top-level `code` on stderr.
|
|
422
|
+
|
|
423
|
+
- `invalid_usage`: required options are missing, duplicated, or unknown (exit 2).
|
|
424
|
+
- `evidence_invalid`: the evidence manifest or a collected file is missing, invalid,
|
|
425
|
+
fails its recorded hash, or a platform's runtime context was unavailable (exit 1).
|
|
426
|
+
- `config_uninspectable`: `config.ts` or `himalaya.content.json` cannot be read, so
|
|
427
|
+
the app's screens and navigation cannot be inspected, or `config.ts` cannot be
|
|
428
|
+
written; in that case the coverage ledger is put back as it was (exit 1).
|
|
429
|
+
- `worker_missing`: the app has no `tier5-src/himi-branded-lite-header-badges`, so a
|
|
430
|
+
config naming that worker would 404; re-scaffold from the current template (exit 1).
|
|
431
|
+
- `config_needs_manual_merge`: `config.ts` already has a `topBarDefault` or
|
|
432
|
+
`navLogicBundle` that differs, or its `navigation` cannot be edited safely; the
|
|
433
|
+
literal to merge is returned in `snippet` and nothing is written (exit 1).
|
|
410
434
|
|
|
411
435
|
## `himi one-app validate-design` codes
|
|
412
436
|
|
|
@@ -533,17 +533,39 @@ Apply these interpretation rules:
|
|
|
533
533
|
without observer/listener fields. Do not infer a count badge for every custom
|
|
534
534
|
component. Inspect its implementation and retain unknown behavior as unresolved.
|
|
535
535
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
536
|
+
**Bell, chat and cart badges are bound by a command, not by hand.** `himi one-app migrate` writes
|
|
537
|
+
`header-badge-plan.json`: the counters the source header carries, found by each button's id, icon and
|
|
538
|
+
accessibility label (the observer or listener id only marks a button as carrying a badge; it does not
|
|
539
|
+
say which counter). A badge button that is not clearly a bell, chat or cart button is listed under
|
|
540
|
+
`unmapped`; its counter stays an open obligation, never guessed. After scaffolding from the Branded Lite
|
|
541
|
+
template and mapping each source screen to its Himi screen (the `route` rows of `ONE-APP-COVERAGE.json`), run
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
himi one-app header-badges --evidence-dir .himi/one-app --content .
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
It adds `navigation.topBarDefault` with one literal action per counter (ids `chat`, `notifications` and
|
|
548
|
+
`cart`, opening `himi_business_chat`, `himi_notifications` and `himi_cart`, each with
|
|
549
|
+
`badge: { kind: "count", valuePath: "$service:brandedLiteNav.<chatBadge|notificationsBadge|cartBadge>" }`)
|
|
550
|
+
and `navigation.navLogicBundle` for the badges-only worker the template already ships under
|
|
551
|
+
`tier5-src/himi-branded-lite-header-badges`, and it records each bound button's `target` in the coverage
|
|
552
|
+
ledger. A source with no badges changes nothing. It never overwrites: a config that already has a
|
|
553
|
+
`topBarDefault` or `navLogicBundle` (and a ledger target you set) is left alone and reported, with the
|
|
554
|
+
literal to merge (`config_needs_manual_merge`). Run it again after mapping routes if you ran it first:
|
|
555
|
+
the rows it wrote move to the mapped screen. Then run `validate-topbar`, which checks that each badge's
|
|
556
|
+
`valuePath` is a count the worker holds and sits on the right button. A screen with its own `topBar`
|
|
557
|
+
replaces `topBarDefault`, so `validate-topbar` still asks that screen to carry the action.
|
|
558
|
+
|
|
559
|
+
The counts: the bell is the member's unseen notifications (read for the site the native bridge is bound
|
|
560
|
+
to), chat is the member's unread business chatrooms (as the server counts them, capped at 100) and cart is
|
|
561
|
+
the shopper's item count (every line's quantity). Each is `null` while unavailable (a Visitor's bell and
|
|
562
|
+
chat, offline, a failed or stale read, an old binary or the web for the bell) and the shell shows no badge
|
|
563
|
+
for `null` or 0, so none is ever a fake zero. If the source shows the bell or chat badge to Visitors, the plan
|
|
564
|
+
warns: a Himi header shows them to signed-in members only. They refresh at launch, whenever the signed-in
|
|
565
|
+
identity changes (the previous identity's numbers go first), on every foreground and on every notification
|
|
566
|
+
receipt, and a read that finishes after the identity changed is dropped. They do not poll: a screen that
|
|
567
|
+
changes the cart (an add-to-cart, a checkout) must call `tier5.brandedLiteNav.refreshBadges` itself, or the
|
|
568
|
+
cart badge waits for the next trigger.
|
|
547
569
|
|
|
548
570
|
Use a live `badge: { kind: "count", valuePath: "$service:sourceBadges.count" }`
|
|
549
571
|
only after implementing the actual source counter. Preserve the source button
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1600.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"artifactId": "himalaya-cli"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
|
-
"falconPackageHash": "
|
|
75
|
+
"falconPackageHash": "345b024a67019de4aeafb2b81fa91e65d43675b03bb58533eefb85a7"
|
|
76
76
|
}
|
|
@@ -42270,6 +42270,72 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
42270
42270
|
}
|
|
42271
42271
|
}
|
|
42272
42272
|
}
|
|
42273
|
+
},
|
|
42274
|
+
{
|
|
42275
|
+
"id": "",
|
|
42276
|
+
"body": {
|
|
42277
|
+
"package": "himalaya",
|
|
42278
|
+
"type": "HStack",
|
|
42279
|
+
"props": {
|
|
42280
|
+
"spacing": 8,
|
|
42281
|
+
"children": [
|
|
42282
|
+
{
|
|
42283
|
+
"id": "himi_blog_post.report",
|
|
42284
|
+
"body": {
|
|
42285
|
+
"package": "himalaya",
|
|
42286
|
+
"type": "Button",
|
|
42287
|
+
"props": {
|
|
42288
|
+
"title": "Report",
|
|
42289
|
+
"style": "secondary",
|
|
42290
|
+
"fullWidth": true,
|
|
42291
|
+
"action": {
|
|
42292
|
+
"actionId": "tier5.himiBlogPost.report"
|
|
42293
|
+
}
|
|
42294
|
+
}
|
|
42295
|
+
},
|
|
42296
|
+
"modifiers": {
|
|
42297
|
+
"hiddenPath": "$service:himiBlogPost.reported"
|
|
42298
|
+
}
|
|
42299
|
+
},
|
|
42300
|
+
{
|
|
42301
|
+
"id": "himi_blog_post.hide",
|
|
42302
|
+
"body": {
|
|
42303
|
+
"package": "himalaya",
|
|
42304
|
+
"type": "Button",
|
|
42305
|
+
"props": {
|
|
42306
|
+
"title": "Hide post",
|
|
42307
|
+
"style": "secondary",
|
|
42308
|
+
"fullWidth": true,
|
|
42309
|
+
"action": {
|
|
42310
|
+
"actionId": "tier5.himiBlogPost.openHide"
|
|
42311
|
+
}
|
|
42312
|
+
}
|
|
42313
|
+
},
|
|
42314
|
+
"modifiers": {
|
|
42315
|
+
"hiddenPath": "$service:himiBlogPost.hidden"
|
|
42316
|
+
}
|
|
42317
|
+
},
|
|
42318
|
+
{
|
|
42319
|
+
"id": "himi_blog_post.unhide",
|
|
42320
|
+
"body": {
|
|
42321
|
+
"package": "himalaya",
|
|
42322
|
+
"type": "Button",
|
|
42323
|
+
"props": {
|
|
42324
|
+
"title": "Undo hide",
|
|
42325
|
+
"style": "secondary",
|
|
42326
|
+
"fullWidth": true,
|
|
42327
|
+
"action": {
|
|
42328
|
+
"actionId": "tier5.himiBlogPost.unhide"
|
|
42329
|
+
}
|
|
42330
|
+
}
|
|
42331
|
+
},
|
|
42332
|
+
"modifiers": {
|
|
42333
|
+
"hiddenPath": "$service:himiBlogPost.notHidden"
|
|
42334
|
+
}
|
|
42335
|
+
}
|
|
42336
|
+
]
|
|
42337
|
+
}
|
|
42338
|
+
}
|
|
42273
42339
|
}
|
|
42274
42340
|
]
|
|
42275
42341
|
}
|
|
@@ -42528,6 +42594,122 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
42528
42594
|
"hiddenPath": "$service:himiBlogPost.noPost"
|
|
42529
42595
|
}
|
|
42530
42596
|
},
|
|
42597
|
+
{
|
|
42598
|
+
"id": "himi_blog_post.hide_sheet",
|
|
42599
|
+
"body": {
|
|
42600
|
+
"package": "himalaya",
|
|
42601
|
+
"type": "BottomSheet",
|
|
42602
|
+
"props": {
|
|
42603
|
+
"presentedPath": "$service:himiBlogPost.hideSheetOpen",
|
|
42604
|
+
"dismissible": true,
|
|
42605
|
+
"showGrabber": true,
|
|
42606
|
+
"onDismiss": {
|
|
42607
|
+
"actionId": "tier5.himiBlogPost.closeHide"
|
|
42608
|
+
},
|
|
42609
|
+
"content": {
|
|
42610
|
+
"id": "",
|
|
42611
|
+
"body": {
|
|
42612
|
+
"package": "himalaya",
|
|
42613
|
+
"type": "VStack",
|
|
42614
|
+
"props": {
|
|
42615
|
+
"spacing": 12,
|
|
42616
|
+
"alignment": "leading",
|
|
42617
|
+
"children": [
|
|
42618
|
+
{
|
|
42619
|
+
"id": "",
|
|
42620
|
+
"body": {
|
|
42621
|
+
"package": "himalaya",
|
|
42622
|
+
"type": "Text",
|
|
42623
|
+
"props": {
|
|
42624
|
+
"text": "Why are you hiding this post?",
|
|
42625
|
+
"style": "title3"
|
|
42626
|
+
}
|
|
42627
|
+
}
|
|
42628
|
+
},
|
|
42629
|
+
{
|
|
42630
|
+
"id": "himi_blog_post.hide_offensive",
|
|
42631
|
+
"body": {
|
|
42632
|
+
"package": "himalaya",
|
|
42633
|
+
"type": "Button",
|
|
42634
|
+
"props": {
|
|
42635
|
+
"title": "It is offensive",
|
|
42636
|
+
"style": "secondary",
|
|
42637
|
+
"fullWidth": true,
|
|
42638
|
+
"action": {
|
|
42639
|
+
"actionId": "tier5.himiBlogPost.hide",
|
|
42640
|
+
"params": {
|
|
42641
|
+
"reason": "offensive"
|
|
42642
|
+
}
|
|
42643
|
+
}
|
|
42644
|
+
}
|
|
42645
|
+
}
|
|
42646
|
+
},
|
|
42647
|
+
{
|
|
42648
|
+
"id": "himi_blog_post.hide_misleading",
|
|
42649
|
+
"body": {
|
|
42650
|
+
"package": "himalaya",
|
|
42651
|
+
"type": "Button",
|
|
42652
|
+
"props": {
|
|
42653
|
+
"title": "It is misleading",
|
|
42654
|
+
"style": "secondary",
|
|
42655
|
+
"fullWidth": true,
|
|
42656
|
+
"action": {
|
|
42657
|
+
"actionId": "tier5.himiBlogPost.hide",
|
|
42658
|
+
"params": {
|
|
42659
|
+
"reason": "misleading"
|
|
42660
|
+
}
|
|
42661
|
+
}
|
|
42662
|
+
}
|
|
42663
|
+
}
|
|
42664
|
+
},
|
|
42665
|
+
{
|
|
42666
|
+
"id": "himi_blog_post.hide_other",
|
|
42667
|
+
"body": {
|
|
42668
|
+
"package": "himalaya",
|
|
42669
|
+
"type": "Button",
|
|
42670
|
+
"props": {
|
|
42671
|
+
"title": "Something else",
|
|
42672
|
+
"style": "secondary",
|
|
42673
|
+
"fullWidth": true,
|
|
42674
|
+
"action": {
|
|
42675
|
+
"actionId": "tier5.himiBlogPost.hide",
|
|
42676
|
+
"params": {
|
|
42677
|
+
"reason": "other"
|
|
42678
|
+
}
|
|
42679
|
+
}
|
|
42680
|
+
}
|
|
42681
|
+
}
|
|
42682
|
+
},
|
|
42683
|
+
{
|
|
42684
|
+
"id": "himi_blog_post.hide_cancel",
|
|
42685
|
+
"body": {
|
|
42686
|
+
"package": "himalaya",
|
|
42687
|
+
"type": "Button",
|
|
42688
|
+
"props": {
|
|
42689
|
+
"title": "Cancel",
|
|
42690
|
+
"style": "secondary",
|
|
42691
|
+
"fullWidth": true,
|
|
42692
|
+
"action": {
|
|
42693
|
+
"actionId": "tier5.himiBlogPost.closeHide"
|
|
42694
|
+
}
|
|
42695
|
+
}
|
|
42696
|
+
}
|
|
42697
|
+
}
|
|
42698
|
+
]
|
|
42699
|
+
}
|
|
42700
|
+
},
|
|
42701
|
+
"modifiers": {
|
|
42702
|
+
"padding": {
|
|
42703
|
+
"leading": 20,
|
|
42704
|
+
"trailing": 20,
|
|
42705
|
+
"top": 16,
|
|
42706
|
+
"bottom": 24
|
|
42707
|
+
}
|
|
42708
|
+
}
|
|
42709
|
+
}
|
|
42710
|
+
}
|
|
42711
|
+
}
|
|
42712
|
+
},
|
|
42531
42713
|
{
|
|
42532
42714
|
"id": "",
|
|
42533
42715
|
"body": {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// The posts this device's reader chose to hide, as the One App blog does: a client-side list of post ids that the
|
|
2
|
+
// blog feed, the Home cards and Saved leave out. Nothing is sent to the site, and another device or account does not
|
|
3
|
+
// share it. It is the saved-posts store's twin (see himi-blog-saved.ts) and reads storage the same way: native workers
|
|
4
|
+
// have only the sync `himalaya.storage` KV, a browser has `localStorage`, and each screen runs its own worker, so
|
|
5
|
+
// the durable arm is what lets a Hide on the post screen reach the feed's worker.
|
|
6
|
+
const KEY = "himi-blog:hidden-posts:v1";
|
|
7
|
+
|
|
8
|
+
type StorageLike = {
|
|
9
|
+
getItem: (key: string) => string | null;
|
|
10
|
+
setItem: (key: string, value: string) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type HiddenHost = typeof globalThis & {
|
|
14
|
+
himalaya?: { storage?: StorageLike };
|
|
15
|
+
localStorage?: StorageLike;
|
|
16
|
+
__himiBlogHiddenIds?: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const host = globalThis as HiddenHost;
|
|
20
|
+
|
|
21
|
+
function storage(): StorageLike | undefined {
|
|
22
|
+
return host.himalaya?.storage ?? host.localStorage;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const unique = (ids: string[]): string[] => Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
|
|
26
|
+
|
|
27
|
+
export function readHiddenIds(): string[] {
|
|
28
|
+
try {
|
|
29
|
+
const raw = storage()?.getItem(KEY);
|
|
30
|
+
if (raw) {
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
if (Array.isArray(parsed)) return unique(parsed.filter((id): id is string => typeof id === "string"));
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Unreadable storage: fall back to what this worker context holds in memory.
|
|
36
|
+
}
|
|
37
|
+
return unique(host.__himiBlogHiddenIds ?? []);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeHiddenIds(ids: string[]): void {
|
|
41
|
+
const next = unique(ids);
|
|
42
|
+
host.__himiBlogHiddenIds = next;
|
|
43
|
+
try {
|
|
44
|
+
storage()?.setItem(KEY, JSON.stringify(next));
|
|
45
|
+
} catch {
|
|
46
|
+
// Storage-less hosts keep the in-memory arm only.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isHiddenId(id: string): boolean {
|
|
51
|
+
return readHiddenIds().includes(id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function hideId(id: string): void {
|
|
55
|
+
writeHiddenIds([id, ...readHiddenIds()]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function unhideId(id: string): void {
|
|
59
|
+
writeHiddenIds(readHiddenIds().filter((hiddenId) => hiddenId !== id));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The items a list may show: every one whose `id` this device has not hidden. */
|
|
63
|
+
export function withoutHidden<T extends { id?: string | null }>(items: T[]): T[] {
|
|
64
|
+
const hidden = new Set(readHiddenIds());
|
|
65
|
+
return hidden.size ? items.filter((item) => !item.id || !hidden.has(item.id)) : items;
|
|
66
|
+
}
|
|
@@ -4,6 +4,7 @@ import { queryCategories, queryPosts, type WixPost } from "@himalaya/wix-blog";
|
|
|
4
4
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
5
5
|
import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
6
6
|
import { formatDateMedium } from "../_shared/engine-intl.js";
|
|
7
|
+
import { withoutHidden } from "../_shared/himi-blog-hidden.js";
|
|
7
8
|
|
|
8
9
|
const PAGE_SIZE = 20;
|
|
9
10
|
|
|
@@ -137,7 +138,8 @@ function normalize(post: WixPost, categoryMap: Map<string, string>): PostRow {
|
|
|
137
138
|
|
|
138
139
|
function applyFilters(s: S): S {
|
|
139
140
|
const needle = s.query.trim().toLowerCase();
|
|
140
|
-
|
|
141
|
+
// Posts this device's reader hid (on the post screen) never show, whatever the cache or the live page holds.
|
|
142
|
+
const posts = withoutHidden(s.allPosts).filter((post) => {
|
|
141
143
|
const categoryOk = !s.selectedCategoryId || post.categoryId === s.selectedCategoryId;
|
|
142
144
|
const queryOk =
|
|
143
145
|
!needle ||
|
|
@@ -5,6 +5,7 @@ import { getPost, likePost, unlikePost, type WixPost, type WixPostWithContent }
|
|
|
5
5
|
import { comments, BLOG_COMMENTS_APP_ID, blogCommentRow, listBlogComments, type WixBlogCommentRow } from "@himalaya/wix-comments";
|
|
6
6
|
import { get, type DbEntity } from "@himalaya/local-db";
|
|
7
7
|
import { isSavedId, toggleSavedId } from "../_shared/himi-blog-saved";
|
|
8
|
+
import { hideId, isHiddenId, unhideId } from "../_shared/himi-blog-hidden";
|
|
8
9
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
9
10
|
import { formatDateMedium, formatMonthDay } from "../_shared/engine-intl.js";
|
|
10
11
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
@@ -64,6 +65,14 @@ interface S {
|
|
|
64
65
|
notLiked: boolean;
|
|
65
66
|
saved: boolean;
|
|
66
67
|
notSaved: boolean;
|
|
68
|
+
/** This device's reader hid the post: the feed, Home and Saved leave it out, and this screen offers Undo. */
|
|
69
|
+
hidden: boolean;
|
|
70
|
+
notHidden: boolean;
|
|
71
|
+
/** The reason sheet (offensive / misleading / other) is open. */
|
|
72
|
+
hideSheetOpen: boolean;
|
|
73
|
+
/** The reader reported the post this visit; the report button then says so and stops sending. */
|
|
74
|
+
reported: boolean;
|
|
75
|
+
notReported: boolean;
|
|
67
76
|
commentDraft: string;
|
|
68
77
|
comments: CommentRow[];
|
|
69
78
|
commentsUnavailable: boolean;
|
|
@@ -212,6 +221,8 @@ function derive(s: S): S {
|
|
|
212
221
|
hasBodyJson: s.bodyJson !== "",
|
|
213
222
|
notLiked: !s.liked,
|
|
214
223
|
notSaved: !s.saved,
|
|
224
|
+
notHidden: !s.hidden,
|
|
225
|
+
notReported: !s.reported,
|
|
215
226
|
commentsAvailable: !s.commentsUnavailable,
|
|
216
227
|
};
|
|
217
228
|
}
|
|
@@ -238,6 +249,11 @@ const baseState: S = derive({
|
|
|
238
249
|
notLiked: true,
|
|
239
250
|
saved: false,
|
|
240
251
|
notSaved: true,
|
|
252
|
+
hidden: false,
|
|
253
|
+
notHidden: true,
|
|
254
|
+
hideSheetOpen: false,
|
|
255
|
+
reported: false,
|
|
256
|
+
notReported: true,
|
|
241
257
|
commentDraft: "",
|
|
242
258
|
comments: [],
|
|
243
259
|
commentsUnavailable: false,
|
|
@@ -278,6 +294,7 @@ async function cachedPostState(s: S, id: string): Promise<S> {
|
|
|
278
294
|
initial: String(cached.initial ?? "") || (title[0] || "#").toUpperCase(),
|
|
279
295
|
liked: false,
|
|
280
296
|
saved: isSavedId(id),
|
|
297
|
+
hidden: isHiddenId(id),
|
|
281
298
|
comments: [],
|
|
282
299
|
commentsUnavailable: true,
|
|
283
300
|
commentStatusText: copyParam("blogPost.commentsUnavailable", "Comments are unavailable right now."),
|
|
@@ -331,6 +348,7 @@ async function loadPostState(s: S): Promise<S | undefined> {
|
|
|
331
348
|
initial: (title[0] || "#").toUpperCase(),
|
|
332
349
|
liked: Boolean(post.memberInteraction?.liked),
|
|
333
350
|
saved: isSavedId(id),
|
|
351
|
+
hidden: isHiddenId(id),
|
|
334
352
|
comments,
|
|
335
353
|
commentsUnavailable,
|
|
336
354
|
commentStatusText: commentsUnavailable
|
|
@@ -359,6 +377,14 @@ const svc = defineService<S, {
|
|
|
359
377
|
like: (s: S) => Promise<S>;
|
|
360
378
|
unlike: (s: S) => Promise<S>;
|
|
361
379
|
toggleSave: (s: S) => S;
|
|
380
|
+
/** Report the post: no site API takes a member's report, so the reader is thanked and the tap is the signal. */
|
|
381
|
+
report: (s: S) => S;
|
|
382
|
+
openHide: (s: S) => S;
|
|
383
|
+
closeHide: (s: S) => S;
|
|
384
|
+
/** Hide the post on this device. The sheet's three reasons (offensive, misleading, other) are asked for as the One
|
|
385
|
+
* App asks them, but the list keeps only the id and the site is told nothing, so the worker does not read the reason. */
|
|
386
|
+
hide: (s: S) => S;
|
|
387
|
+
unhide: (s: S) => S;
|
|
362
388
|
share: (s: S) => Promise<S>;
|
|
363
389
|
setComment: (s: S, arg?: unknown) => S;
|
|
364
390
|
submitComment: (s: S) => Promise<S>;
|
|
@@ -397,6 +423,22 @@ const svc = defineService<S, {
|
|
|
397
423
|
const saved = toggleSavedId(s.id);
|
|
398
424
|
return derive({ ...s, saved, actionMessage: saved ? copyParam("blogPost.savedMessage", "Saved for later") : copyParam("blogPost.unsavedMessage", "Removed from saved") });
|
|
399
425
|
},
|
|
426
|
+
report: (s) => {
|
|
427
|
+
if (!s.id || s.reported) return s;
|
|
428
|
+
return derive({ ...s, reported: true, actionMessage: copyParam("blogPost.reportedMessage", "Post reported") });
|
|
429
|
+
},
|
|
430
|
+
openHide: (s) => s.id ? derive({ ...s, hideSheetOpen: true }) : s,
|
|
431
|
+
closeHide: (s) => derive({ ...s, hideSheetOpen: false }),
|
|
432
|
+
hide: (s) => {
|
|
433
|
+
if (!s.id) return s;
|
|
434
|
+
hideId(s.id);
|
|
435
|
+
return derive({ ...s, hidden: true, hideSheetOpen: false, actionMessage: copyParam("blogPost.hiddenMessage", "Post hidden. It will not show in your feeds.") });
|
|
436
|
+
},
|
|
437
|
+
unhide: (s) => {
|
|
438
|
+
if (!s.id) return s;
|
|
439
|
+
unhideId(s.id);
|
|
440
|
+
return derive({ ...s, hidden: false, actionMessage: copyParam("blogPost.unhiddenMessage", "Post is visible again") });
|
|
441
|
+
},
|
|
400
442
|
share: async (s) => {
|
|
401
443
|
if (!s.title) return s;
|
|
402
444
|
try {
|
|
@@ -3,6 +3,7 @@ import { defineService } from "@himalaya/state";
|
|
|
3
3
|
import { getPost, type WixPost } from "@himalaya/wix-blog";
|
|
4
4
|
import { get, type DbEntity } from "@himalaya/local-db";
|
|
5
5
|
import { readSavedIds, writeSavedIds } from "../_shared/himi-blog-saved";
|
|
6
|
+
import { readHiddenIds } from "../_shared/himi-blog-hidden";
|
|
6
7
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
7
8
|
import { formatDateMedium } from "../_shared/engine-intl.js";
|
|
8
9
|
|
|
@@ -144,7 +145,9 @@ const baseState: S = derive({
|
|
|
144
145
|
});
|
|
145
146
|
|
|
146
147
|
async function loadSavedState(s: S): Promise<S> {
|
|
147
|
-
|
|
148
|
+
// A saved post this device's reader also hid stays in the saved list (Undo brings it back) but is not shown.
|
|
149
|
+
const hidden = new Set(readHiddenIds());
|
|
150
|
+
const ids = readSavedIds().filter((id) => !hidden.has(id));
|
|
148
151
|
if (!ids.length) {
|
|
149
152
|
return derive({ ...s, status: "ready", posts: [], emptyText: copyParam("saved.emptyText", "No saved posts yet.") });
|
|
150
153
|
}
|
|
@@ -26,6 +26,7 @@ import { formatDateMedium } from "../_shared/engine-intl.js";
|
|
|
26
26
|
import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-saved.js";
|
|
27
27
|
import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
|
|
28
28
|
import { readSavedIds } from "../_shared/himi-blog-saved.js";
|
|
29
|
+
import { withoutHidden } from "../_shared/himi-blog-hidden.js";
|
|
29
30
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
30
31
|
import { groupImage, programImage } from "../_shared/himi-media.js";
|
|
31
32
|
import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
|
|
@@ -770,8 +771,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
|
|
|
770
771
|
// read used to make; a failure collapses both blocks silently.
|
|
771
772
|
jobs.push(queryPosts({ query: { paging: { limit: 8 }, sort: [{ fieldName: "firstPublishedDate", order: "DESC" }] } })
|
|
772
773
|
.then((response) => {
|
|
773
|
-
|
|
774
|
-
|
|
774
|
+
// Posts this device's reader hid on the post screen are left out of both blocks.
|
|
775
|
+
const posts = withoutHidden(response.posts ?? []);
|
|
776
|
+
extras.announcementTitle = cleanTitle(posts[0]?.title);
|
|
777
|
+
extras.postCards = posts.filter((post) => post.id).slice(0, 6).map(postCard);
|
|
775
778
|
})
|
|
776
779
|
.catch(() => { /* collapse silently */ }));
|
|
777
780
|
}
|