mechanica 2.0.0-alpha.8 → 2.0.0-alpha.9
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.js +10 -4
- package/dist/editor.css +82 -58
- package/dist/editor.js +102 -62
- package/dist/plugin.js +2 -0
- package/dist/types/cli/export.d.ts +10 -1
- package/dist/types/vite/entries.d.ts +3 -0
- package/dist/types/vite/plugin.d.ts +15 -0
- package/package.json +2 -2
- package/src/cli/cli.ts +8 -2
- package/src/cli/export.ts +18 -2
- package/src/editor/dialogs/PagesDialog.vue +94 -9
- package/src/vite/entries.ts +4 -0
- package/src/vite/plugin.ts +16 -0
package/dist/cli.js
CHANGED
|
@@ -302,7 +302,8 @@ async function backfillImageMeta(pages, blocksMap, mechDir, warn) {
|
|
|
302
302
|
* `<cwd>/.mech`.
|
|
303
303
|
*
|
|
304
304
|
* Beyond the pages it also: rewrites + copies uploaded assets
|
|
305
|
-
* (`/@mechanica/assets/…` → `/media
|
|
305
|
+
* (`/@mechanica/assets/…` → `/media/…`, or `<assetsUrl>/media/…` when a CDN
|
|
306
|
+
* base is set), warns about dead internal links,
|
|
306
307
|
* orphaned uploads and page-level SEO issues, emits `404.html` when a `/404`
|
|
307
308
|
* page exists, and — when a site url is known (plugin `siteUrl` option or
|
|
308
309
|
* `--site-url`) — injects the automatic SEO tags (canonical, `og:url`,
|
|
@@ -314,6 +315,7 @@ async function exportProject(cwd, ssr, options = {}) {
|
|
|
314
315
|
const index = await readFile(join(cwd, "dist/index.html"), "utf-8");
|
|
315
316
|
const siteUrl = options.siteUrl ?? ssr.site?.url;
|
|
316
317
|
const siteName = options.siteName ?? ssr.site?.name;
|
|
318
|
+
const assetsBase = (options.assetsUrl ?? ssr.assetsUrl ?? "").replace(/\/+$/, "");
|
|
317
319
|
const composedDefs = ssr.composedList ?? [];
|
|
318
320
|
const blocks = [...ssr.blocksList.map(toBlockMeta), ...composedDefs.map(composedBlockMeta)];
|
|
319
321
|
const blocksMap = new Map(blocks.map((block) => [block.id, block]));
|
|
@@ -420,7 +422,7 @@ async function exportProject(cwd, ssr, options = {}) {
|
|
|
420
422
|
if (typeof src !== "string" || !src.startsWith("/@mechanica/assets/")) return src;
|
|
421
423
|
const relative = decodeURIComponent(src.slice(UPLOADS_PREFIX.length));
|
|
422
424
|
referenced.add(relative);
|
|
423
|
-
return
|
|
425
|
+
return `${assetsBase}/${MEDIA_DIR}/${relative}`;
|
|
424
426
|
};
|
|
425
427
|
const blockAssets = await readBlockAssets(cwd);
|
|
426
428
|
const pageLinks = blockAssets ? (content) => {
|
|
@@ -447,6 +449,7 @@ async function exportProject(cwd, ssr, options = {}) {
|
|
|
447
449
|
url: siteUrl,
|
|
448
450
|
name: siteName
|
|
449
451
|
},
|
|
452
|
+
assetsUrl: assetsBase || void 0,
|
|
450
453
|
locales: config ?? void 0,
|
|
451
454
|
render: (state) => ssr.render(state, { resolveQuery: resolveForPage(state.page?.pagination?.page ?? 1, state.page?.locale) }),
|
|
452
455
|
onFile,
|
|
@@ -1364,7 +1367,9 @@ Usage:
|
|
|
1364
1367
|
(a site url — the plugin's siteUrl option or
|
|
1365
1368
|
--site-url <origin> — also turns on the automatic
|
|
1366
1369
|
SEO tags and emits sitemap.xml + robots.txt;
|
|
1367
|
-
--site-name <name> adds WebSite JSON-LD
|
|
1370
|
+
--site-name <name> adds WebSite JSON-LD;
|
|
1371
|
+
--assets-url <origin> serves /assets and /media from
|
|
1372
|
+
a CDN base — files still land in export/)
|
|
1368
1373
|
mechanica push [--key] Upload dist/ to a backend (--key, --host, --dir)
|
|
1369
1374
|
mechanica shot <blockId> Screenshot one block via the dev preview route
|
|
1370
1375
|
mechanica shot </path> Screenshot a whole page (editor overlay stripped)
|
|
@@ -1389,7 +1394,8 @@ async function run(argv) {
|
|
|
1389
1394
|
case "build": return runBuild();
|
|
1390
1395
|
case "export": return runExport({
|
|
1391
1396
|
siteUrl: str(flags["site-url"]),
|
|
1392
|
-
siteName: str(flags["site-name"])
|
|
1397
|
+
siteName: str(flags["site-name"]),
|
|
1398
|
+
assetsUrl: str(flags["assets-url"])
|
|
1393
1399
|
});
|
|
1394
1400
|
case "images": return runImages({ force: flags.force === true });
|
|
1395
1401
|
case "push": return runPush({
|
package/dist/editor.css
CHANGED
|
@@ -4802,7 +4802,7 @@ from {
|
|
|
4802
4802
|
justify-content: flex-end;
|
|
4803
4803
|
gap: 8px;
|
|
4804
4804
|
margin-top: 4px;
|
|
4805
|
-
}.mech-pages[data-v-
|
|
4805
|
+
}.mech-pages[data-v-665bb42e] {
|
|
4806
4806
|
display: flex;
|
|
4807
4807
|
align-items: stretch;
|
|
4808
4808
|
justify-content: center;
|
|
@@ -4811,7 +4811,7 @@ from {
|
|
|
4811
4811
|
max-width: 1200px;
|
|
4812
4812
|
height: min(620px, 82vh);
|
|
4813
4813
|
}
|
|
4814
|
-
.mech-pages__card[data-v-
|
|
4814
|
+
.mech-pages__card[data-v-665bb42e] {
|
|
4815
4815
|
display: flex;
|
|
4816
4816
|
flex-direction: column;
|
|
4817
4817
|
flex: 0 1 840px;
|
|
@@ -4821,20 +4821,20 @@ from {
|
|
|
4821
4821
|
box-shadow: var(--mech-shadow-dialog);
|
|
4822
4822
|
overflow: hidden;
|
|
4823
4823
|
}
|
|
4824
|
-
.mech-pages__header[data-v-
|
|
4824
|
+
.mech-pages__header[data-v-665bb42e] {
|
|
4825
4825
|
display: flex;
|
|
4826
4826
|
align-items: center;
|
|
4827
4827
|
gap: 10px;
|
|
4828
4828
|
padding: 18px 14px 12px 24px;
|
|
4829
4829
|
flex: none;
|
|
4830
4830
|
}
|
|
4831
|
-
.mech-pages__title[data-v-
|
|
4831
|
+
.mech-pages__title[data-v-665bb42e] {
|
|
4832
4832
|
margin: 0;
|
|
4833
4833
|
font-size: 16px;
|
|
4834
4834
|
font-weight: 600;
|
|
4835
4835
|
letter-spacing: -0.01em;
|
|
4836
4836
|
}
|
|
4837
|
-
.mech-pages__count[data-v-
|
|
4837
|
+
.mech-pages__count[data-v-665bb42e] {
|
|
4838
4838
|
padding: 2px 8px;
|
|
4839
4839
|
border-radius: var(--mech-radius-pill);
|
|
4840
4840
|
background: var(--mech-active);
|
|
@@ -4842,7 +4842,7 @@ from {
|
|
|
4842
4842
|
font-size: 11.5px;
|
|
4843
4843
|
font-weight: 600;
|
|
4844
4844
|
}
|
|
4845
|
-
.mech-pages__close[data-v-
|
|
4845
|
+
.mech-pages__close[data-v-665bb42e] {
|
|
4846
4846
|
display: flex;
|
|
4847
4847
|
align-items: center;
|
|
4848
4848
|
justify-content: center;
|
|
@@ -4855,25 +4855,49 @@ from {
|
|
|
4855
4855
|
border-radius: var(--mech-radius-pill);
|
|
4856
4856
|
cursor: pointer;
|
|
4857
4857
|
}
|
|
4858
|
-
.mech-pages__close .vicon[data-v-
|
|
4858
|
+
.mech-pages__close .vicon[data-v-665bb42e] {
|
|
4859
4859
|
width: 18px;
|
|
4860
4860
|
height: 18px;
|
|
4861
4861
|
}
|
|
4862
|
-
.mech-pages__close[data-v-
|
|
4862
|
+
.mech-pages__close[data-v-665bb42e]:hover {
|
|
4863
4863
|
background: var(--mech-hover);
|
|
4864
4864
|
color: var(--mech-fg);
|
|
4865
4865
|
}
|
|
4866
|
-
.mech-pages__toolbar[data-v-
|
|
4866
|
+
.mech-pages__toolbar[data-v-665bb42e] {
|
|
4867
4867
|
display: flex;
|
|
4868
4868
|
gap: 8px;
|
|
4869
|
-
padding: 0 24px
|
|
4869
|
+
padding: 0 24px 12px;
|
|
4870
4870
|
flex: none;
|
|
4871
4871
|
}
|
|
4872
|
-
.mech-pages__search-wrap[data-v-
|
|
4872
|
+
.mech-pages__search-wrap[data-v-665bb42e] {
|
|
4873
4873
|
position: relative;
|
|
4874
4874
|
flex: 1;
|
|
4875
4875
|
}
|
|
4876
|
-
.mech-
|
|
4876
|
+
.mech-pages__filters[data-v-665bb42e] {
|
|
4877
|
+
display: flex;
|
|
4878
|
+
align-items: center;
|
|
4879
|
+
justify-content: space-between;
|
|
4880
|
+
gap: 12px;
|
|
4881
|
+
padding: 0 24px 14px;
|
|
4882
|
+
flex: none;
|
|
4883
|
+
}
|
|
4884
|
+
.mech-pages__sort[data-v-665bb42e] {
|
|
4885
|
+
display: flex;
|
|
4886
|
+
align-items: center;
|
|
4887
|
+
gap: 9px;
|
|
4888
|
+
}
|
|
4889
|
+
.mech-pages__sort-label[data-v-665bb42e] {
|
|
4890
|
+
font-size: 10.5px;
|
|
4891
|
+
font-weight: 600;
|
|
4892
|
+
text-transform: uppercase;
|
|
4893
|
+
letter-spacing: 0.05em;
|
|
4894
|
+
color: var(--mech-muted);
|
|
4895
|
+
}
|
|
4896
|
+
.mech-pages__toggle[data-v-665bb42e] {
|
|
4897
|
+
flex: none;
|
|
4898
|
+
white-space: nowrap;
|
|
4899
|
+
}
|
|
4900
|
+
.mech-pages__search-icon[data-v-665bb42e] {
|
|
4877
4901
|
position: absolute;
|
|
4878
4902
|
left: 12px;
|
|
4879
4903
|
top: 50%;
|
|
@@ -4883,18 +4907,18 @@ from {
|
|
|
4883
4907
|
color: var(--mech-muted);
|
|
4884
4908
|
pointer-events: none;
|
|
4885
4909
|
}
|
|
4886
|
-
.mech-pages__search[data-v-
|
|
4910
|
+
.mech-pages__search[data-v-665bb42e] {
|
|
4887
4911
|
width: 100%;
|
|
4888
4912
|
padding-left: 36px;
|
|
4889
4913
|
}
|
|
4890
|
-
.mech-pages__content[data-v-
|
|
4914
|
+
.mech-pages__content[data-v-665bb42e] {
|
|
4891
4915
|
display: flex;
|
|
4892
4916
|
gap: 14px;
|
|
4893
4917
|
flex: 1;
|
|
4894
4918
|
min-height: 0;
|
|
4895
4919
|
padding: 0 24px 20px;
|
|
4896
4920
|
}
|
|
4897
|
-
.mech-pages__rail[data-v-
|
|
4921
|
+
.mech-pages__rail[data-v-665bb42e] {
|
|
4898
4922
|
flex: none;
|
|
4899
4923
|
width: 172px;
|
|
4900
4924
|
display: flex;
|
|
@@ -4903,7 +4927,7 @@ from {
|
|
|
4903
4927
|
overflow-y: auto;
|
|
4904
4928
|
padding-right: 2px;
|
|
4905
4929
|
}
|
|
4906
|
-
.mech-pages__rail-item[data-v-
|
|
4930
|
+
.mech-pages__rail-item[data-v-665bb42e] {
|
|
4907
4931
|
display: flex;
|
|
4908
4932
|
align-items: center;
|
|
4909
4933
|
gap: 8px;
|
|
@@ -4917,51 +4941,51 @@ from {
|
|
|
4917
4941
|
color: var(--mech-fg-alt);
|
|
4918
4942
|
cursor: pointer;
|
|
4919
4943
|
}
|
|
4920
|
-
.mech-pages__rail-item .vicon[data-v-
|
|
4944
|
+
.mech-pages__rail-item .vicon[data-v-665bb42e] {
|
|
4921
4945
|
flex: none;
|
|
4922
4946
|
width: 15px;
|
|
4923
4947
|
height: 15px;
|
|
4924
4948
|
color: var(--mech-muted);
|
|
4925
4949
|
}
|
|
4926
|
-
.mech-pages__rail-item[data-v-
|
|
4950
|
+
.mech-pages__rail-item[data-v-665bb42e]:hover {
|
|
4927
4951
|
background: var(--mech-hover);
|
|
4928
4952
|
}
|
|
4929
|
-
.mech-pages__rail-item.is-active[data-v-
|
|
4953
|
+
.mech-pages__rail-item.is-active[data-v-665bb42e] {
|
|
4930
4954
|
background: var(--mech-accent-soft);
|
|
4931
4955
|
color: var(--mech-accent);
|
|
4932
4956
|
font-weight: 500;
|
|
4933
4957
|
}
|
|
4934
|
-
.mech-pages__rail-item.is-active .vicon[data-v-
|
|
4958
|
+
.mech-pages__rail-item.is-active .vicon[data-v-665bb42e] {
|
|
4935
4959
|
color: var(--mech-accent);
|
|
4936
4960
|
}
|
|
4937
|
-
.mech-pages__rail-name[data-v-
|
|
4961
|
+
.mech-pages__rail-name[data-v-665bb42e] {
|
|
4938
4962
|
flex: 1;
|
|
4939
4963
|
min-width: 0;
|
|
4940
4964
|
overflow: hidden;
|
|
4941
4965
|
text-overflow: ellipsis;
|
|
4942
4966
|
white-space: nowrap;
|
|
4943
4967
|
}
|
|
4944
|
-
.mech-pages__rail-count[data-v-
|
|
4968
|
+
.mech-pages__rail-count[data-v-665bb42e] {
|
|
4945
4969
|
flex: none;
|
|
4946
4970
|
font-size: 11px;
|
|
4947
4971
|
color: var(--mech-placeholder);
|
|
4948
4972
|
}
|
|
4949
|
-
.mech-pages__rail-item.is-active .mech-pages__rail-count[data-v-
|
|
4973
|
+
.mech-pages__rail-item.is-active .mech-pages__rail-count[data-v-665bb42e] {
|
|
4950
4974
|
color: var(--mech-accent);
|
|
4951
4975
|
opacity: 0.7;
|
|
4952
4976
|
}
|
|
4953
|
-
.mech-pages__list[data-v-
|
|
4977
|
+
.mech-pages__list[data-v-665bb42e] {
|
|
4954
4978
|
flex: 1;
|
|
4955
4979
|
min-width: 0;
|
|
4956
4980
|
overflow-y: auto;
|
|
4957
4981
|
}
|
|
4958
|
-
.mech-pages__row[data-v-
|
|
4982
|
+
.mech-pages__row[data-v-665bb42e], .mech-pages__thead[data-v-665bb42e] {
|
|
4959
4983
|
display: grid;
|
|
4960
4984
|
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) 96px;
|
|
4961
4985
|
align-items: center;
|
|
4962
4986
|
column-gap: 12px;
|
|
4963
4987
|
}
|
|
4964
|
-
.mech-pages__thead[data-v-
|
|
4988
|
+
.mech-pages__thead[data-v-665bb42e] {
|
|
4965
4989
|
position: sticky;
|
|
4966
4990
|
top: 0;
|
|
4967
4991
|
z-index: 1;
|
|
@@ -4969,14 +4993,14 @@ from {
|
|
|
4969
4993
|
background: var(--mech-bg);
|
|
4970
4994
|
box-shadow: 0 1px 0 var(--mech-border);
|
|
4971
4995
|
}
|
|
4972
|
-
.mech-pages__thead span[data-v-
|
|
4996
|
+
.mech-pages__thead span[data-v-665bb42e] {
|
|
4973
4997
|
font-size: 10.5px;
|
|
4974
4998
|
font-weight: 600;
|
|
4975
4999
|
text-transform: uppercase;
|
|
4976
5000
|
letter-spacing: 0.05em;
|
|
4977
5001
|
color: var(--mech-muted);
|
|
4978
5002
|
}
|
|
4979
|
-
.mech-pages__group[data-v-
|
|
5003
|
+
.mech-pages__group[data-v-665bb42e] {
|
|
4980
5004
|
display: flex;
|
|
4981
5005
|
align-items: center;
|
|
4982
5006
|
gap: 7px;
|
|
@@ -4995,36 +5019,36 @@ from {
|
|
|
4995
5019
|
border-radius: var(--mech-radius-sm);
|
|
4996
5020
|
cursor: pointer;
|
|
4997
5021
|
}
|
|
4998
|
-
.mech-pages__group .vicon[data-v-
|
|
5022
|
+
.mech-pages__group .vicon[data-v-665bb42e] {
|
|
4999
5023
|
width: 13px;
|
|
5000
5024
|
height: 13px;
|
|
5001
5025
|
}
|
|
5002
|
-
.mech-pages__group[data-v-
|
|
5026
|
+
.mech-pages__group[data-v-665bb42e]:hover {
|
|
5003
5027
|
color: var(--mech-fg);
|
|
5004
5028
|
}
|
|
5005
|
-
.mech-pages__row[data-v-
|
|
5029
|
+
.mech-pages__row[data-v-665bb42e] {
|
|
5006
5030
|
padding: 0 10px;
|
|
5007
5031
|
height: 38px;
|
|
5008
5032
|
border-radius: var(--mech-radius-sm);
|
|
5009
5033
|
cursor: pointer;
|
|
5010
5034
|
}
|
|
5011
|
-
.mech-pages__row.is-active[data-v-
|
|
5035
|
+
.mech-pages__row.is-active[data-v-665bb42e] {
|
|
5012
5036
|
background: var(--mech-accent-soft);
|
|
5013
5037
|
}
|
|
5014
|
-
.mech-pages__cell-name[data-v-
|
|
5038
|
+
.mech-pages__cell-name[data-v-665bb42e] {
|
|
5015
5039
|
display: flex;
|
|
5016
5040
|
align-items: center;
|
|
5017
5041
|
gap: 8px;
|
|
5018
5042
|
min-width: 0;
|
|
5019
5043
|
}
|
|
5020
|
-
.mech-pages__name[data-v-
|
|
5044
|
+
.mech-pages__name[data-v-665bb42e] {
|
|
5021
5045
|
font-size: 13.5px;
|
|
5022
5046
|
font-weight: 500;
|
|
5023
5047
|
overflow: hidden;
|
|
5024
5048
|
text-overflow: ellipsis;
|
|
5025
5049
|
white-space: nowrap;
|
|
5026
5050
|
}
|
|
5027
|
-
.mech-pages__badge[data-v-
|
|
5051
|
+
.mech-pages__badge[data-v-665bb42e] {
|
|
5028
5052
|
flex: none;
|
|
5029
5053
|
font-size: 10px;
|
|
5030
5054
|
font-weight: 600;
|
|
@@ -5036,17 +5060,17 @@ from {
|
|
|
5036
5060
|
border-radius: var(--mech-radius-pill);
|
|
5037
5061
|
padding: 1px 7px;
|
|
5038
5062
|
}
|
|
5039
|
-
.mech-pages__badge.is-draft[data-v-
|
|
5063
|
+
.mech-pages__badge.is-draft[data-v-665bb42e] {
|
|
5040
5064
|
color: var(--mech-muted);
|
|
5041
5065
|
}
|
|
5042
|
-
.mech-pages__locales[data-v-
|
|
5066
|
+
.mech-pages__locales[data-v-665bb42e] {
|
|
5043
5067
|
flex: none;
|
|
5044
5068
|
display: inline-flex;
|
|
5045
5069
|
align-items: center;
|
|
5046
5070
|
gap: 3px;
|
|
5047
5071
|
margin-left: 2px;
|
|
5048
5072
|
}
|
|
5049
|
-
.mech-pages__loc[data-v-
|
|
5073
|
+
.mech-pages__loc[data-v-665bb42e] {
|
|
5050
5074
|
min-width: 15px;
|
|
5051
5075
|
padding: 1px 4px;
|
|
5052
5076
|
border-radius: var(--mech-radius-sm);
|
|
@@ -5060,12 +5084,12 @@ from {
|
|
|
5060
5084
|
letter-spacing: 0.02em;
|
|
5061
5085
|
opacity: 0.5;
|
|
5062
5086
|
}
|
|
5063
|
-
.mech-pages__loc.is-on[data-v-
|
|
5087
|
+
.mech-pages__loc.is-on[data-v-665bb42e] {
|
|
5064
5088
|
background: var(--mech-accent-soft);
|
|
5065
5089
|
color: var(--mech-accent);
|
|
5066
5090
|
opacity: 1;
|
|
5067
5091
|
}
|
|
5068
|
-
.mech-pages__cell-path[data-v-
|
|
5092
|
+
.mech-pages__cell-path[data-v-665bb42e] {
|
|
5069
5093
|
min-width: 0;
|
|
5070
5094
|
overflow: hidden;
|
|
5071
5095
|
text-overflow: ellipsis;
|
|
@@ -5073,16 +5097,16 @@ from {
|
|
|
5073
5097
|
font-size: 12.5px;
|
|
5074
5098
|
color: var(--mech-muted);
|
|
5075
5099
|
}
|
|
5076
|
-
.mech-pages__actions[data-v-
|
|
5100
|
+
.mech-pages__actions[data-v-665bb42e] {
|
|
5077
5101
|
display: flex;
|
|
5078
5102
|
justify-content: flex-end;
|
|
5079
5103
|
gap: 1px;
|
|
5080
5104
|
opacity: 0;
|
|
5081
5105
|
}
|
|
5082
|
-
.mech-pages__row.is-active .mech-pages__actions[data-v-
|
|
5106
|
+
.mech-pages__row.is-active .mech-pages__actions[data-v-665bb42e], .mech-pages__row:hover .mech-pages__actions[data-v-665bb42e] {
|
|
5083
5107
|
opacity: 1;
|
|
5084
5108
|
}
|
|
5085
|
-
.mech-pages__actions button[data-v-
|
|
5109
|
+
.mech-pages__actions button[data-v-665bb42e] {
|
|
5086
5110
|
display: flex;
|
|
5087
5111
|
align-items: center;
|
|
5088
5112
|
justify-content: center;
|
|
@@ -5095,24 +5119,24 @@ from {
|
|
|
5095
5119
|
cursor: pointer;
|
|
5096
5120
|
line-height: 1;
|
|
5097
5121
|
}
|
|
5098
|
-
.mech-pages__actions button .vicon[data-v-
|
|
5122
|
+
.mech-pages__actions button .vicon[data-v-665bb42e] {
|
|
5099
5123
|
width: 15px;
|
|
5100
5124
|
height: 15px;
|
|
5101
5125
|
}
|
|
5102
|
-
.mech-pages__actions button[data-v-
|
|
5126
|
+
.mech-pages__actions button[data-v-665bb42e]:hover {
|
|
5103
5127
|
background: var(--mech-active);
|
|
5104
5128
|
color: var(--mech-fg);
|
|
5105
5129
|
}
|
|
5106
|
-
.mech-pages__actions button.is-danger[data-v-
|
|
5130
|
+
.mech-pages__actions button.is-danger[data-v-665bb42e]:hover {
|
|
5107
5131
|
color: var(--mech-error);
|
|
5108
5132
|
}
|
|
5109
|
-
.mech-pages__empty[data-v-
|
|
5133
|
+
.mech-pages__empty[data-v-665bb42e] {
|
|
5110
5134
|
padding: 24px 10px;
|
|
5111
5135
|
margin: 0;
|
|
5112
5136
|
color: var(--mech-muted);
|
|
5113
5137
|
font-size: 13px;
|
|
5114
5138
|
}
|
|
5115
|
-
.mech-pages__preview[data-v-
|
|
5139
|
+
.mech-pages__preview[data-v-665bb42e] {
|
|
5116
5140
|
flex: none;
|
|
5117
5141
|
align-self: flex-start;
|
|
5118
5142
|
max-height: 100%;
|
|
@@ -5124,18 +5148,18 @@ from {
|
|
|
5124
5148
|
box-shadow: var(--mech-shadow-dialog);
|
|
5125
5149
|
overflow: hidden;
|
|
5126
5150
|
}
|
|
5127
|
-
.mech-pages__preview-frame[data-v-
|
|
5151
|
+
.mech-pages__preview-frame[data-v-665bb42e] {
|
|
5128
5152
|
flex: 0 1 auto;
|
|
5129
5153
|
min-height: 0;
|
|
5130
5154
|
overflow: hidden;
|
|
5131
5155
|
background: var(--mech-app-bg);
|
|
5132
5156
|
}
|
|
5133
|
-
.mech-pages__preview-frame img[data-v-
|
|
5157
|
+
.mech-pages__preview-frame img[data-v-665bb42e] {
|
|
5134
5158
|
display: block;
|
|
5135
5159
|
width: 100%;
|
|
5136
5160
|
height: auto;
|
|
5137
5161
|
}
|
|
5138
|
-
.mech-pages__preview-empty[data-v-
|
|
5162
|
+
.mech-pages__preview-empty[data-v-665bb42e] {
|
|
5139
5163
|
display: flex;
|
|
5140
5164
|
flex-direction: column;
|
|
5141
5165
|
align-items: center;
|
|
@@ -5144,16 +5168,16 @@ from {
|
|
|
5144
5168
|
padding: 48px 16px;
|
|
5145
5169
|
color: var(--mech-muted);
|
|
5146
5170
|
}
|
|
5147
|
-
.mech-pages__preview-empty p[data-v-
|
|
5171
|
+
.mech-pages__preview-empty p[data-v-665bb42e] {
|
|
5148
5172
|
margin: 8px 0 0;
|
|
5149
5173
|
font-size: 13px;
|
|
5150
5174
|
font-weight: 500;
|
|
5151
5175
|
}
|
|
5152
|
-
.mech-pages__preview-empty code[data-v-
|
|
5176
|
+
.mech-pages__preview-empty code[data-v-665bb42e] {
|
|
5153
5177
|
font-size: 11.5px;
|
|
5154
5178
|
color: var(--mech-placeholder);
|
|
5155
5179
|
}
|
|
5156
|
-
.mech-pages__preview-monogram[data-v-
|
|
5180
|
+
.mech-pages__preview-monogram[data-v-665bb42e] {
|
|
5157
5181
|
display: flex;
|
|
5158
5182
|
align-items: center;
|
|
5159
5183
|
justify-content: center;
|
|
@@ -5164,7 +5188,7 @@ from {
|
|
|
5164
5188
|
font-size: 22px;
|
|
5165
5189
|
font-weight: 600;
|
|
5166
5190
|
}
|
|
5167
|
-
.mech-pages__preview-caption[data-v-
|
|
5191
|
+
.mech-pages__preview-caption[data-v-665bb42e] {
|
|
5168
5192
|
flex: none;
|
|
5169
5193
|
display: flex;
|
|
5170
5194
|
flex-direction: column;
|
|
@@ -5172,14 +5196,14 @@ from {
|
|
|
5172
5196
|
padding: 12px 16px 14px;
|
|
5173
5197
|
border-top: 1px solid var(--mech-border);
|
|
5174
5198
|
}
|
|
5175
|
-
.mech-pages__preview-name[data-v-
|
|
5199
|
+
.mech-pages__preview-name[data-v-665bb42e] {
|
|
5176
5200
|
font-size: 13px;
|
|
5177
5201
|
font-weight: 600;
|
|
5178
5202
|
overflow: hidden;
|
|
5179
5203
|
text-overflow: ellipsis;
|
|
5180
5204
|
white-space: nowrap;
|
|
5181
5205
|
}
|
|
5182
|
-
.mech-pages__preview-path[data-v-
|
|
5206
|
+
.mech-pages__preview-path[data-v-665bb42e] {
|
|
5183
5207
|
font-size: 11.5px;
|
|
5184
5208
|
color: var(--mech-muted);
|
|
5185
5209
|
overflow: hidden;
|
|
@@ -5187,7 +5211,7 @@ from {
|
|
|
5187
5211
|
white-space: nowrap;
|
|
5188
5212
|
}
|
|
5189
5213
|
@media (max-width: 1160px) {
|
|
5190
|
-
.mech-pages__preview[data-v-
|
|
5214
|
+
.mech-pages__preview[data-v-665bb42e] {
|
|
5191
5215
|
display: none;
|
|
5192
5216
|
}
|
|
5193
5217
|
}.mech-pagebar[data-v-a0f85ad0] {
|
package/dist/editor.js
CHANGED
|
@@ -3,7 +3,7 @@ import { f as createRouter, h as mechanicaKey, i as renderBlocks, n as pushState
|
|
|
3
3
|
import { i as getDataEntries, t as buildPreviewContent } from "./chunks/preview-G5_AFI5P.js";
|
|
4
4
|
import { A as groupPagesByFolder, C as registerBuiltinFieldEditors, D as VSegmented_default, E as createContextMenu, M as pathFromName, O as fetchPages, P as VPopover_default, T as contextMenuKey, _ as findParentSlot, b as relocateBlock, c as compareBlocks, f as cloneBlock, h as findBlock, j as pageThumbUrl, k as filterPages, l as createContentBlock, n as createSaveQueue, o as shortcutChar, p as duplicateBlock, r as SchemaForm_default, s as blockAvailableIn, t as SaveConflictError, u as toBlockMeta, v as moveBlock, w as registerRichTextWidgets, x as removeBlock, y as placeBlock } from "./chunks/editor-9tquvDcS.js";
|
|
5
5
|
import { c as useDialog, d as _plugin_vue_export_helper_default, l as VIcon_default, n as VDialog_default, o as createDialogStore, s as dialogKey } from "./chunks/ImagePickerDialog-CY-mn2UP.js";
|
|
6
|
-
import { Fragment, Teleport, Transition, TransitionGroup, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, markRaw, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, onScopeDispose, openBlock, provide, reactive, ref, renderList, resolveComponent, resolveDynamicComponent, shallowRef, toDisplayString, unref, useTemplateRef, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
|
|
6
|
+
import { Fragment, Teleport, Transition, TransitionGroup, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, markRaw, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, onScopeDispose, openBlock, provide, reactive, ref, renderList, resolveComponent, resolveDynamicComponent, shallowRef, toDisplayString, unref, useTemplateRef, vModelCheckbox, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
|
|
7
7
|
import { getDefaultValue, localeLabel, localePath, parseLocalePath, registerFieldSchemas } from "mechanica-shared";
|
|
8
8
|
import { blocksList } from "virtual:mechanica/blocks";
|
|
9
9
|
import { composedList } from "virtual:mechanica/composed";
|
|
@@ -1886,62 +1886,70 @@ var _hoisted_3$3 = { class: "mech-pages__header" };
|
|
|
1886
1886
|
var _hoisted_4$3 = { class: "mech-pages__count" };
|
|
1887
1887
|
var _hoisted_5$2 = { class: "mech-pages__toolbar" };
|
|
1888
1888
|
var _hoisted_6$2 = { class: "mech-pages__search-wrap" };
|
|
1889
|
-
var _hoisted_7$2 = { class: "mech-
|
|
1890
|
-
var _hoisted_8$2 = {
|
|
1889
|
+
var _hoisted_7$2 = { class: "mech-pages__filters" };
|
|
1890
|
+
var _hoisted_8$2 = { class: "mech-pages__sort" };
|
|
1891
|
+
var _hoisted_9$2 = {
|
|
1892
|
+
key: 0,
|
|
1893
|
+
class: "mech-checkbox mech-pages__toggle"
|
|
1894
|
+
};
|
|
1895
|
+
var _hoisted_10$2 = { class: "mech-pages__content" };
|
|
1896
|
+
var _hoisted_11$1 = {
|
|
1891
1897
|
key: 0,
|
|
1892
1898
|
class: "mech-pages__rail"
|
|
1893
1899
|
};
|
|
1894
|
-
var _hoisted_9$2 = { class: "mech-pages__rail-count" };
|
|
1895
|
-
var _hoisted_10$2 = ["onClick"];
|
|
1896
|
-
var _hoisted_11$1 = { class: "mech-pages__rail-name" };
|
|
1897
1900
|
var _hoisted_12 = { class: "mech-pages__rail-count" };
|
|
1898
1901
|
var _hoisted_13 = ["onClick"];
|
|
1899
|
-
var _hoisted_14 =
|
|
1902
|
+
var _hoisted_14 = { class: "mech-pages__rail-name" };
|
|
1903
|
+
var _hoisted_15 = { class: "mech-pages__rail-count" };
|
|
1904
|
+
var _hoisted_16 = ["onClick"];
|
|
1905
|
+
var _hoisted_17 = [
|
|
1900
1906
|
"onClick",
|
|
1901
1907
|
"onMouseenter",
|
|
1902
1908
|
"onContextmenu"
|
|
1903
1909
|
];
|
|
1904
|
-
var
|
|
1905
|
-
var
|
|
1906
|
-
var
|
|
1910
|
+
var _hoisted_18 = { class: "mech-pages__cell-name" };
|
|
1911
|
+
var _hoisted_19 = { class: "mech-pages__name" };
|
|
1912
|
+
var _hoisted_20 = {
|
|
1907
1913
|
key: 0,
|
|
1908
1914
|
class: "mech-pages__badge is-draft"
|
|
1909
1915
|
};
|
|
1910
|
-
var
|
|
1916
|
+
var _hoisted_21 = {
|
|
1911
1917
|
key: 1,
|
|
1912
1918
|
class: "mech-pages__badge"
|
|
1913
1919
|
};
|
|
1914
|
-
var
|
|
1920
|
+
var _hoisted_22 = {
|
|
1915
1921
|
key: 2,
|
|
1916
1922
|
class: "mech-pages__badge"
|
|
1917
1923
|
};
|
|
1918
|
-
var
|
|
1924
|
+
var _hoisted_23 = {
|
|
1919
1925
|
key: 3,
|
|
1920
1926
|
class: "mech-pages__locales"
|
|
1921
1927
|
};
|
|
1922
|
-
var
|
|
1923
|
-
var
|
|
1924
|
-
var
|
|
1925
|
-
var
|
|
1926
|
-
var
|
|
1927
|
-
var
|
|
1928
|
+
var _hoisted_24 = ["title"];
|
|
1929
|
+
var _hoisted_25 = { class: "mech-pages__cell-path" };
|
|
1930
|
+
var _hoisted_26 = ["onClick"];
|
|
1931
|
+
var _hoisted_27 = ["onClick"];
|
|
1932
|
+
var _hoisted_28 = ["onClick"];
|
|
1933
|
+
var _hoisted_29 = {
|
|
1928
1934
|
key: 0,
|
|
1929
1935
|
class: "mech-pages__empty"
|
|
1930
1936
|
};
|
|
1931
|
-
var
|
|
1932
|
-
var
|
|
1933
|
-
var
|
|
1934
|
-
var
|
|
1937
|
+
var _hoisted_30 = { class: "mech-pages__preview" };
|
|
1938
|
+
var _hoisted_31 = { class: "mech-pages__preview-frame" };
|
|
1939
|
+
var _hoisted_32 = ["src", "data-path"];
|
|
1940
|
+
var _hoisted_33 = {
|
|
1935
1941
|
key: 1,
|
|
1936
1942
|
class: "mech-pages__preview-empty"
|
|
1937
1943
|
};
|
|
1938
|
-
var
|
|
1939
|
-
var
|
|
1944
|
+
var _hoisted_34 = { class: "mech-pages__preview-monogram" };
|
|
1945
|
+
var _hoisted_35 = {
|
|
1940
1946
|
key: 0,
|
|
1941
1947
|
class: "mech-pages__preview-caption"
|
|
1942
1948
|
};
|
|
1943
|
-
var
|
|
1944
|
-
var
|
|
1949
|
+
var _hoisted_36 = { class: "mech-pages__preview-name" };
|
|
1950
|
+
var _hoisted_37 = { class: "mech-pages__preview-path" };
|
|
1951
|
+
var SHOW_GENERATED_KEY = "mech:pages:show-generated";
|
|
1952
|
+
var SORT_KEY = "mech:pages:sort";
|
|
1945
1953
|
//#endregion
|
|
1946
1954
|
//#region src/editor/dialogs/PagesDialog.vue
|
|
1947
1955
|
var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
@@ -1963,20 +1971,42 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
1963
1971
|
const searchInput = useTemplateRef("searchInput");
|
|
1964
1972
|
const listEl = useTemplateRef("listEl");
|
|
1965
1973
|
const searching = computed(() => !!query.value.trim());
|
|
1974
|
+
const showGenerated = ref(typeof localStorage === "undefined" || localStorage.getItem(SHOW_GENERATED_KEY) !== "0");
|
|
1975
|
+
watch(showGenerated, (value) => {
|
|
1976
|
+
try {
|
|
1977
|
+
localStorage.setItem(SHOW_GENERATED_KEY, value ? "1" : "0");
|
|
1978
|
+
} catch {}
|
|
1979
|
+
});
|
|
1980
|
+
const hasGenerated = computed(() => pages.value.some((page) => page.generated));
|
|
1981
|
+
const listedPages = computed(() => showGenerated.value ? pages.value : pages.value.filter((page) => !page.generated));
|
|
1982
|
+
const sortKey = ref((typeof localStorage !== "undefined" && localStorage.getItem(SORT_KEY)) === "name" ? "name" : "path");
|
|
1983
|
+
watch(sortKey, (value) => {
|
|
1984
|
+
try {
|
|
1985
|
+
localStorage.setItem(SORT_KEY, value);
|
|
1986
|
+
} catch {}
|
|
1987
|
+
});
|
|
1988
|
+
const sortOptions = [{
|
|
1989
|
+
value: "path",
|
|
1990
|
+
label: "Path"
|
|
1991
|
+
}, {
|
|
1992
|
+
value: "name",
|
|
1993
|
+
label: "Name"
|
|
1994
|
+
}];
|
|
1966
1995
|
const folders = computed(() => {
|
|
1967
1996
|
const counts = /* @__PURE__ */ new Map();
|
|
1968
|
-
for (const page of
|
|
1997
|
+
for (const page of listedPages.value) if (page.folderPath) counts.set(page.folderPath, (counts.get(page.folderPath) ?? 0) + 1);
|
|
1969
1998
|
return [...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => ({
|
|
1970
1999
|
name,
|
|
1971
2000
|
count
|
|
1972
2001
|
}));
|
|
1973
2002
|
});
|
|
1974
2003
|
const visible = computed(() => {
|
|
1975
|
-
if (searching.value) return filterPages(
|
|
1976
|
-
if (activeFolder.value) return
|
|
1977
|
-
return
|
|
2004
|
+
if (searching.value) return filterPages(listedPages.value, query.value);
|
|
2005
|
+
if (activeFolder.value) return listedPages.value.filter((page) => page.folderPath === activeFolder.value);
|
|
2006
|
+
return listedPages.value;
|
|
1978
2007
|
});
|
|
1979
|
-
const
|
|
2008
|
+
const sorted = computed(() => [...visible.value].sort((a, b) => sortKey.value === "name" ? (a.name || a.path).localeCompare(b.name || b.path) : a.path.localeCompare(b.path)));
|
|
2009
|
+
const groups = computed(() => groupPagesByFolder(sorted.value));
|
|
1980
2010
|
/** Flat list in render order — keyboard navigation walks this. */
|
|
1981
2011
|
const rows = computed(() => groups.value.flatMap((group) => group.pages));
|
|
1982
2012
|
const showGroupHeaders = computed(() => searching.value || !activeFolder.value);
|
|
@@ -1985,7 +2015,7 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
1985
2015
|
onMounted(async () => {
|
|
1986
2016
|
await load();
|
|
1987
2017
|
searchInput.value?.focus();
|
|
1988
|
-
activePath.value =
|
|
2018
|
+
activePath.value = rows.value.some((page) => page.path === current.value) ? current.value : rows.value[0]?.path ?? null;
|
|
1989
2019
|
});
|
|
1990
2020
|
watch(rows, (list) => {
|
|
1991
2021
|
if (!list.some((page) => page.path === activePath.value)) activePath.value = list[0]?.path ?? null;
|
|
@@ -2135,8 +2165,8 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
2135
2165
|
return (_ctx, _cache) => {
|
|
2136
2166
|
return openBlock(), createElementBlock("div", _hoisted_1$4, [createElementVNode("div", _hoisted_2$4, [
|
|
2137
2167
|
createElementVNode("header", _hoisted_3$3, [
|
|
2138
|
-
_cache[
|
|
2139
|
-
createElementVNode("span", _hoisted_4$3, toDisplayString(
|
|
2168
|
+
_cache[6] || (_cache[6] = createElementVNode("h2", { class: "mech-pages__title" }, "Pages", -1)),
|
|
2169
|
+
createElementVNode("span", _hoisted_4$3, toDisplayString(listedPages.value.length), 1),
|
|
2140
2170
|
createElementVNode("button", {
|
|
2141
2171
|
type: "button",
|
|
2142
2172
|
class: "mech-pages__close",
|
|
@@ -2159,15 +2189,25 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
2159
2189
|
type: "button",
|
|
2160
2190
|
class: "mech-button is-primary",
|
|
2161
2191
|
onClick: startCreate
|
|
2162
|
-
}, [createVNode(VIcon_default, { name: "plus" }), _cache[
|
|
2163
|
-
createElementVNode("div", _hoisted_7$2, [
|
|
2192
|
+
}, [createVNode(VIcon_default, { name: "plus" }), _cache[7] || (_cache[7] = createTextVNode(" New page ", -1))])]),
|
|
2193
|
+
createElementVNode("div", _hoisted_7$2, [createElementVNode("div", _hoisted_8$2, [_cache[8] || (_cache[8] = createElementVNode("span", { class: "mech-pages__sort-label" }, "Sort", -1)), createVNode(VSegmented_default, {
|
|
2194
|
+
modelValue: sortKey.value,
|
|
2195
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => sortKey.value = $event),
|
|
2196
|
+
options: sortOptions,
|
|
2197
|
+
size: "sm",
|
|
2198
|
+
"aria-label": "Sort pages"
|
|
2199
|
+
}, null, 8, ["modelValue"])]), hasGenerated.value ? (openBlock(), createElementBlock("label", _hoisted_9$2, [withDirectives(createElementVNode("input", {
|
|
2200
|
+
"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => showGenerated.value = $event),
|
|
2201
|
+
type: "checkbox"
|
|
2202
|
+
}, null, 512), [[vModelCheckbox, showGenerated.value]]), _cache[9] || (_cache[9] = createElementVNode("span", null, "Show generated pages", -1))])) : createCommentVNode("", true)]),
|
|
2203
|
+
createElementVNode("div", _hoisted_10$2, [folders.value.length ? (openBlock(), createElementBlock("nav", _hoisted_11$1, [createElementVNode("button", {
|
|
2164
2204
|
type: "button",
|
|
2165
2205
|
class: normalizeClass(["mech-pages__rail-item", { "is-active": !activeFolder.value && !searching.value }]),
|
|
2166
|
-
onClick: _cache[
|
|
2206
|
+
onClick: _cache[4] || (_cache[4] = ($event) => selectFolder(null))
|
|
2167
2207
|
}, [
|
|
2168
2208
|
createVNode(VIcon_default, { name: "book" }),
|
|
2169
|
-
_cache[
|
|
2170
|
-
createElementVNode("span",
|
|
2209
|
+
_cache[10] || (_cache[10] = createElementVNode("span", { class: "mech-pages__rail-name" }, "All pages", -1)),
|
|
2210
|
+
createElementVNode("span", _hoisted_12, toDisplayString(listedPages.value.length), 1)
|
|
2171
2211
|
], 2), (openBlock(true), createElementBlock(Fragment, null, renderList(folders.value, (folder) => {
|
|
2172
2212
|
return openBlock(), createElementBlock("button", {
|
|
2173
2213
|
key: folder.name,
|
|
@@ -2176,15 +2216,15 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
2176
2216
|
onClick: ($event) => selectFolder(folder.name)
|
|
2177
2217
|
}, [
|
|
2178
2218
|
createVNode(VIcon_default, { name: "folder" }),
|
|
2179
|
-
createElementVNode("span",
|
|
2180
|
-
createElementVNode("span",
|
|
2181
|
-
], 10,
|
|
2219
|
+
createElementVNode("span", _hoisted_14, toDisplayString(folder.name), 1),
|
|
2220
|
+
createElementVNode("span", _hoisted_15, toDisplayString(folder.count), 1)
|
|
2221
|
+
], 10, _hoisted_13);
|
|
2182
2222
|
}), 128))])) : createCommentVNode("", true), createElementVNode("div", {
|
|
2183
2223
|
ref_key: "listEl",
|
|
2184
2224
|
ref: listEl,
|
|
2185
2225
|
class: "mech-pages__list"
|
|
2186
2226
|
}, [
|
|
2187
|
-
_cache[
|
|
2227
|
+
_cache[11] || (_cache[11] = createElementVNode("div", { class: "mech-pages__thead" }, [
|
|
2188
2228
|
createElementVNode("span", null, "Name"),
|
|
2189
2229
|
createElementVNode("span", null, "Path"),
|
|
2190
2230
|
createElementVNode("span", { "aria-hidden": "true" })
|
|
@@ -2196,7 +2236,7 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
2196
2236
|
class: "mech-pages__group",
|
|
2197
2237
|
title: "Show only this folder",
|
|
2198
2238
|
onClick: ($event) => selectFolder(group.folder)
|
|
2199
|
-
}, [createVNode(VIcon_default, { name: "folder" }), createTextVNode(" " + toDisplayString(group.folder), 1)], 8,
|
|
2239
|
+
}, [createVNode(VIcon_default, { name: "folder" }), createTextVNode(" " + toDisplayString(group.folder), 1)], 8, _hoisted_16)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(group.pages, (page) => {
|
|
2200
2240
|
return openBlock(), createElementBlock("div", {
|
|
2201
2241
|
key: page.path,
|
|
2202
2242
|
class: normalizeClass(["mech-pages__row", {
|
|
@@ -2207,57 +2247,57 @@ var PagesDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
|
|
|
2207
2247
|
onMouseenter: ($event) => activePath.value = page.path,
|
|
2208
2248
|
onContextmenu: ($event) => onRowMenu($event, page)
|
|
2209
2249
|
}, [
|
|
2210
|
-
createElementVNode("span",
|
|
2211
|
-
createElementVNode("span",
|
|
2212
|
-
page.draft ? (openBlock(), createElementBlock("span",
|
|
2213
|
-
page.generated ? (openBlock(), createElementBlock("span",
|
|
2214
|
-
page.path === currentLogical.value ? (openBlock(), createElementBlock("span",
|
|
2215
|
-
unref(localeConfig) && page.locales ? (openBlock(), createElementBlock("span",
|
|
2250
|
+
createElementVNode("span", _hoisted_18, [
|
|
2251
|
+
createElementVNode("span", _hoisted_19, toDisplayString(page.name || page.path), 1),
|
|
2252
|
+
page.draft ? (openBlock(), createElementBlock("span", _hoisted_20, "Draft")) : createCommentVNode("", true),
|
|
2253
|
+
page.generated ? (openBlock(), createElementBlock("span", _hoisted_21, "Generated")) : createCommentVNode("", true),
|
|
2254
|
+
page.path === currentLogical.value ? (openBlock(), createElementBlock("span", _hoisted_22, "Current")) : createCommentVNode("", true),
|
|
2255
|
+
unref(localeConfig) && page.locales ? (openBlock(), createElementBlock("span", _hoisted_23, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(localeCodes), (code) => {
|
|
2216
2256
|
return openBlock(), createElementBlock("span", {
|
|
2217
2257
|
key: code,
|
|
2218
2258
|
class: normalizeClass(["mech-pages__loc", { "is-on": page.locales.includes(code) }]),
|
|
2219
2259
|
title: `${localeName(code)}: ${page.locales.includes(code) ? "translated" : "not translated"}`
|
|
2220
|
-
}, toDisplayString(code), 11,
|
|
2260
|
+
}, toDisplayString(code), 11, _hoisted_24);
|
|
2221
2261
|
}), 128))])) : createCommentVNode("", true)
|
|
2222
2262
|
]),
|
|
2223
|
-
createElementVNode("span",
|
|
2263
|
+
createElementVNode("span", _hoisted_25, toDisplayString(page.path), 1),
|
|
2224
2264
|
!page.generated ? (openBlock(), createElementBlock("span", {
|
|
2225
2265
|
key: 0,
|
|
2226
2266
|
class: "mech-pages__actions",
|
|
2227
|
-
onClick: _cache[
|
|
2267
|
+
onClick: _cache[5] || (_cache[5] = withModifiers(() => {}, ["stop"]))
|
|
2228
2268
|
}, [
|
|
2229
2269
|
createElementVNode("button", {
|
|
2230
2270
|
type: "button",
|
|
2231
2271
|
title: "Rename",
|
|
2232
2272
|
onClick: ($event) => startEdit(page)
|
|
2233
|
-
}, [createVNode(VIcon_default, { name: "pencil" })], 8,
|
|
2273
|
+
}, [createVNode(VIcon_default, { name: "pencil" })], 8, _hoisted_26),
|
|
2234
2274
|
createElementVNode("button", {
|
|
2235
2275
|
type: "button",
|
|
2236
2276
|
title: "Duplicate",
|
|
2237
2277
|
onClick: ($event) => startDuplicate(page)
|
|
2238
|
-
}, [createVNode(VIcon_default, { name: "copy" })], 8,
|
|
2278
|
+
}, [createVNode(VIcon_default, { name: "copy" })], 8, _hoisted_27),
|
|
2239
2279
|
createElementVNode("button", {
|
|
2240
2280
|
type: "button",
|
|
2241
2281
|
title: "Delete",
|
|
2242
2282
|
class: "is-danger",
|
|
2243
2283
|
onClick: ($event) => confirmRemove(page)
|
|
2244
|
-
}, [createVNode(VIcon_default, { name: "trash" })], 8,
|
|
2284
|
+
}, [createVNode(VIcon_default, { name: "trash" })], 8, _hoisted_28)
|
|
2245
2285
|
])) : createCommentVNode("", true)
|
|
2246
|
-
], 42,
|
|
2286
|
+
], 42, _hoisted_17);
|
|
2247
2287
|
}), 128))], 64);
|
|
2248
2288
|
}), 128)),
|
|
2249
|
-
!rows.value.length ? (openBlock(), createElementBlock("p",
|
|
2289
|
+
!rows.value.length ? (openBlock(), createElementBlock("p", _hoisted_29, toDisplayString(query.value ? `No pages match “${query.value}”.` : "No pages yet."), 1)) : createCommentVNode("", true)
|
|
2250
2290
|
], 512)])
|
|
2251
|
-
]), createElementVNode("aside",
|
|
2291
|
+
]), createElementVNode("aside", _hoisted_30, [createElementVNode("div", _hoisted_31, [previewPage.value && !thumbFailed.has(previewPage.value.path) ? (openBlock(), createElementBlock("img", {
|
|
2252
2292
|
key: previewPage.value.path,
|
|
2253
2293
|
src: thumbSrc(previewPage.value),
|
|
2254
2294
|
"data-path": previewPage.value.path,
|
|
2255
2295
|
alt: "",
|
|
2256
2296
|
onError: onThumbError
|
|
2257
|
-
}, null, 40,
|
|
2297
|
+
}, null, 40, _hoisted_32)) : (openBlock(), createElementBlock("div", _hoisted_33, [createElementVNode("span", _hoisted_34, toDisplayString(monogram.value), 1), previewPage.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[12] || (_cache[12] = createElementVNode("p", null, "No preview yet", -1)), _cache[13] || (_cache[13] = createElementVNode("code", null, "mechanica thumbs", -1))], 64)) : createCommentVNode("", true)]))]), previewPage.value ? (openBlock(), createElementBlock("div", _hoisted_35, [createElementVNode("span", _hoisted_36, toDisplayString(previewPage.value.name || previewPage.value.path), 1), createElementVNode("span", _hoisted_37, toDisplayString(previewPage.value.path), 1)])) : createCommentVNode("", true)])]);
|
|
2258
2298
|
};
|
|
2259
2299
|
}
|
|
2260
|
-
}), [["__scopeId", "data-v-
|
|
2300
|
+
}), [["__scopeId", "data-v-665bb42e"]]);
|
|
2261
2301
|
//#endregion
|
|
2262
2302
|
//#region src/editor/components/PageBar.vue?vue&type=script&setup=true&lang.ts
|
|
2263
2303
|
var _hoisted_1$3 = { class: "mech-pagebar__icon" };
|
package/dist/plugin.js
CHANGED
|
@@ -775,6 +775,7 @@ function generateSsrEntry(options) {
|
|
|
775
775
|
``,
|
|
776
776
|
`registerComponents(blocksMap)`,
|
|
777
777
|
`export const site = ${JSON.stringify(options.site ?? {})}`,
|
|
778
|
+
`export const assetsUrl = ${JSON.stringify(options.assetsUrl ?? null)}`,
|
|
778
779
|
`export const locales = ${JSON.stringify(options.locales ?? null)}`,
|
|
779
780
|
`export const generatedPages = ${JSON.stringify(options.generatedPages ?? [])}`,
|
|
780
781
|
`export const dataEntries = getDataEntries()`,
|
|
@@ -1689,6 +1690,7 @@ function mechanica(options = {}) {
|
|
|
1689
1690
|
url: options.siteUrl,
|
|
1690
1691
|
name: options.siteName
|
|
1691
1692
|
},
|
|
1693
|
+
assetsUrl: options.assetsUrl,
|
|
1692
1694
|
locales,
|
|
1693
1695
|
generatedPages: await loadGeneratedPages()
|
|
1694
1696
|
});
|
|
@@ -22,6 +22,8 @@ export interface SsrBundle {
|
|
|
22
22
|
url?: string;
|
|
23
23
|
name?: string;
|
|
24
24
|
};
|
|
25
|
+
/** CDN base URL for static assets, baked from the plugin's `assetsUrl` option. */
|
|
26
|
+
assetsUrl?: string | null;
|
|
25
27
|
/** The site's locale config baked in from the plugin's `locales` option. */
|
|
26
28
|
locales?: LocalesConfig | null;
|
|
27
29
|
/** Programmatically generated pages (plugin `generatePages`), baked as plain
|
|
@@ -37,6 +39,12 @@ export interface ExportOptions {
|
|
|
37
39
|
siteUrl?: string;
|
|
38
40
|
/** Site display name (WebSite JSON-LD). Overrides the plugin's `siteName`. */
|
|
39
41
|
siteName?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Base URL (CDN origin) to serve static assets from — prefixes both the build
|
|
44
|
+
* assets (`/assets/…`) and uploaded media (`/media/…`). Overrides the plugin's
|
|
45
|
+
* `assetsUrl`. Files still land in `export/`; upload them to the CDN yourself.
|
|
46
|
+
*/
|
|
47
|
+
assetsUrl?: string;
|
|
40
48
|
/** Warning sink (broken links, orphaned assets). Defaults to `console.warn`. */
|
|
41
49
|
onWarn?: (message: string) => void;
|
|
42
50
|
}
|
|
@@ -49,7 +57,8 @@ export interface ExportOptions {
|
|
|
49
57
|
* `<cwd>/.mech`.
|
|
50
58
|
*
|
|
51
59
|
* Beyond the pages it also: rewrites + copies uploaded assets
|
|
52
|
-
* (`/@mechanica/assets/…` → `/media
|
|
60
|
+
* (`/@mechanica/assets/…` → `/media/…`, or `<assetsUrl>/media/…` when a CDN
|
|
61
|
+
* base is set), warns about dead internal links,
|
|
53
62
|
* orphaned uploads and page-level SEO issues, emits `404.html` when a `/404`
|
|
54
63
|
* page exists, and — when a site url is known (plugin `siteUrl` option or
|
|
55
64
|
* `--site-url`) — injects the automatic SEO tags (canonical, `og:url`,
|
|
@@ -58,6 +58,9 @@ export interface SsrEntryOptions {
|
|
|
58
58
|
url?: string;
|
|
59
59
|
name?: string;
|
|
60
60
|
};
|
|
61
|
+
/** CDN base URL for static assets (plugin `assetsUrl`) — rides the bundle so
|
|
62
|
+
* `mechanica export` can serve `/assets/…` and `/media/…` from it. */
|
|
63
|
+
assetsUrl?: string;
|
|
61
64
|
/** The site's locale config (multi-language) — rides the bundle so the export
|
|
62
65
|
* can walk translations, and gets baked into each page's `state.locales`. */
|
|
63
66
|
locales?: import('mechanica-shared').LocalesConfig | null;
|
|
@@ -50,6 +50,21 @@ export interface MechanicaPluginOptions {
|
|
|
50
50
|
siteUrl?: string;
|
|
51
51
|
/** The site's display name — `{{ site.name }}` + WebSite JSON-LD at export. */
|
|
52
52
|
siteName?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Base URL to serve static assets from at export — a CDN origin like
|
|
55
|
+
* `https://cdn.example.com`. When set, `mechanica export` rewrites both the
|
|
56
|
+
* build assets (`/assets/…`) and uploaded media (`/media/…`) to
|
|
57
|
+
* `<assetsUrl>/assets/…` and `<assetsUrl>/media/…`. Files are still emitted
|
|
58
|
+
* into `export/` — upload them to the CDN yourself (e.g. in CI). A trailing
|
|
59
|
+
* slash is ignored. `mechanica export --assets-url <origin>` overrides it.
|
|
60
|
+
*
|
|
61
|
+
* Only affects the static export; the dev server keeps serving assets
|
|
62
|
+
* locally. For build assets referenced from inside CSS/JS chunks (fonts,
|
|
63
|
+
* `url(...)` backgrounds) on a cross-origin CDN, set Vite's `base` instead —
|
|
64
|
+
* this option rewrites HTML asset tags and uploaded media, not chunk-internal
|
|
65
|
+
* references.
|
|
66
|
+
*/
|
|
67
|
+
assetsUrl?: string;
|
|
53
68
|
/**
|
|
54
69
|
* Multi-language configuration. Either a full config
|
|
55
70
|
* (`{ default: 'en', all: ['en', 'ru'], labels? }`) or a bare list of codes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mechanica",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.9",
|
|
4
4
|
"description": "Build Vue 3 websites with a visual block editor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"prepublishOnly": "bun run build"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"mechanica-shared": "2.0.0-alpha.
|
|
66
|
+
"mechanica-shared": "2.0.0-alpha.9",
|
|
67
67
|
"@vue/compiler-sfc": "^3.5.38",
|
|
68
68
|
"compact-json-schema": "^0.1.5",
|
|
69
69
|
"estree-walker": "^3.0.3",
|
package/src/cli/cli.ts
CHANGED
|
@@ -14,7 +14,9 @@ Usage:
|
|
|
14
14
|
(a site url — the plugin's siteUrl option or
|
|
15
15
|
--site-url <origin> — also turns on the automatic
|
|
16
16
|
SEO tags and emits sitemap.xml + robots.txt;
|
|
17
|
-
--site-name <name> adds WebSite JSON-LD
|
|
17
|
+
--site-name <name> adds WebSite JSON-LD;
|
|
18
|
+
--assets-url <origin> serves /assets and /media from
|
|
19
|
+
a CDN base — files still land in export/)
|
|
18
20
|
mechanica push [--key] Upload dist/ to a backend (--key, --host, --dir)
|
|
19
21
|
mechanica shot <blockId> Screenshot one block via the dev preview route
|
|
20
22
|
mechanica shot </path> Screenshot a whole page (editor overlay stripped)
|
|
@@ -42,7 +44,11 @@ export async function run(argv: string[]): Promise<void> {
|
|
|
42
44
|
case 'build':
|
|
43
45
|
return runBuild()
|
|
44
46
|
case 'export':
|
|
45
|
-
return runExport({
|
|
47
|
+
return runExport({
|
|
48
|
+
siteUrl: str(flags['site-url']),
|
|
49
|
+
siteName: str(flags['site-name']),
|
|
50
|
+
assetsUrl: str(flags['assets-url']),
|
|
51
|
+
})
|
|
46
52
|
case 'images':
|
|
47
53
|
return runImages({ force: flags.force === true })
|
|
48
54
|
case 'push':
|
package/src/cli/export.ts
CHANGED
|
@@ -227,6 +227,8 @@ export interface SsrBundle {
|
|
|
227
227
|
dataEntries?: { id: string; props: any }[]
|
|
228
228
|
/** Site identity baked in from the plugin's `siteUrl` / `siteName` options. */
|
|
229
229
|
site?: { url?: string; name?: string }
|
|
230
|
+
/** CDN base URL for static assets, baked from the plugin's `assetsUrl` option. */
|
|
231
|
+
assetsUrl?: string | null
|
|
230
232
|
/** The site's locale config baked in from the plugin's `locales` option. */
|
|
231
233
|
locales?: LocalesConfig | null
|
|
232
234
|
/** Programmatically generated pages (plugin `generatePages`), baked as plain
|
|
@@ -268,6 +270,12 @@ export interface ExportOptions {
|
|
|
268
270
|
siteUrl?: string
|
|
269
271
|
/** Site display name (WebSite JSON-LD). Overrides the plugin's `siteName`. */
|
|
270
272
|
siteName?: string
|
|
273
|
+
/**
|
|
274
|
+
* Base URL (CDN origin) to serve static assets from — prefixes both the build
|
|
275
|
+
* assets (`/assets/…`) and uploaded media (`/media/…`). Overrides the plugin's
|
|
276
|
+
* `assetsUrl`. Files still land in `export/`; upload them to the CDN yourself.
|
|
277
|
+
*/
|
|
278
|
+
assetsUrl?: string
|
|
271
279
|
/** Warning sink (broken links, orphaned assets). Defaults to `console.warn`. */
|
|
272
280
|
onWarn?: (message: string) => void
|
|
273
281
|
}
|
|
@@ -375,7 +383,8 @@ async function backfillImageMeta(
|
|
|
375
383
|
* `<cwd>/.mech`.
|
|
376
384
|
*
|
|
377
385
|
* Beyond the pages it also: rewrites + copies uploaded assets
|
|
378
|
-
* (`/@mechanica/assets/…` → `/media
|
|
386
|
+
* (`/@mechanica/assets/…` → `/media/…`, or `<assetsUrl>/media/…` when a CDN
|
|
387
|
+
* base is set), warns about dead internal links,
|
|
379
388
|
* orphaned uploads and page-level SEO issues, emits `404.html` when a `/404`
|
|
380
389
|
* page exists, and — when a site url is known (plugin `siteUrl` option or
|
|
381
390
|
* `--site-url`) — injects the automatic SEO tags (canonical, `og:url`,
|
|
@@ -393,6 +402,9 @@ export async function exportProject(
|
|
|
393
402
|
// CLI flags win over the site config baked into the SSR bundle.
|
|
394
403
|
const siteUrl = options.siteUrl ?? ssr.site?.url
|
|
395
404
|
const siteName = options.siteName ?? ssr.site?.name
|
|
405
|
+
// CDN base for static assets (plugin `assetsUrl` or `--assets-url`); trim a
|
|
406
|
+
// trailing slash so we build `<base>/media/…` and `<base>/assets/…` cleanly.
|
|
407
|
+
const assetsBase = (options.assetsUrl ?? ssr.assetsUrl ?? '').replace(/\/+$/, '')
|
|
396
408
|
|
|
397
409
|
// Composed blocks join the block set: their (unfolded) schemas fill placed
|
|
398
410
|
// data defaults, and they count as known blocks for link/unknown checks.
|
|
@@ -536,7 +548,8 @@ export async function exportProject(
|
|
|
536
548
|
if (typeof src !== 'string' || !src.startsWith(UPLOADS_PREFIX)) return src
|
|
537
549
|
const relative = decodeURIComponent(src.slice(UPLOADS_PREFIX.length))
|
|
538
550
|
referenced.add(relative)
|
|
539
|
-
|
|
551
|
+
// `assetsBase` is a trimmed CDN origin (or '' — then this stays root-relative).
|
|
552
|
+
return `${assetsBase}/${MEDIA_DIR}/${relative}`
|
|
540
553
|
}
|
|
541
554
|
|
|
542
555
|
// Blocks are code-split in the client build: give each page stylesheet +
|
|
@@ -569,6 +582,9 @@ export async function exportProject(
|
|
|
569
582
|
dataEntries: ssr.dataEntries ?? [],
|
|
570
583
|
projectData,
|
|
571
584
|
site: { url: siteUrl, name: siteName },
|
|
585
|
+
// Serve the build assets (`/assets/…` in the HTML) from the CDN base too, so
|
|
586
|
+
// the whole `export/` tree can be hosted there. Empty = keep them local.
|
|
587
|
+
assetsUrl: assetsBase || undefined,
|
|
572
588
|
// Bake the locale config into each page's `state.locales` so the runtime
|
|
573
589
|
// prefixes internal links for `state.page.locale`.
|
|
574
590
|
locales: config ?? undefined,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="mech-pages__card" role="dialog" aria-modal="true">
|
|
4
4
|
<header class="mech-pages__header">
|
|
5
5
|
<h2 class="mech-pages__title">Pages</h2>
|
|
6
|
-
<span class="mech-pages__count">{{
|
|
6
|
+
<span class="mech-pages__count">{{ listedPages.length }}</span>
|
|
7
7
|
<button type="button" class="mech-pages__close" aria-label="Close" @click="dialog.back()">
|
|
8
8
|
<VIcon name="close" />
|
|
9
9
|
</button>
|
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
</button>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
|
+
<!-- Filter / sort row — a home for sorting and toggles like generated pages. -->
|
|
30
|
+
<div class="mech-pages__filters">
|
|
31
|
+
<div class="mech-pages__sort">
|
|
32
|
+
<span class="mech-pages__sort-label">Sort</span>
|
|
33
|
+
<VSegmented v-model="sortKey" :options="sortOptions" size="sm" aria-label="Sort pages" />
|
|
34
|
+
</div>
|
|
35
|
+
<label v-if="hasGenerated" class="mech-checkbox mech-pages__toggle">
|
|
36
|
+
<input v-model="showGenerated" type="checkbox" />
|
|
37
|
+
<span>Show generated pages</span>
|
|
38
|
+
</label>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
29
41
|
<div class="mech-pages__content">
|
|
30
42
|
<nav v-if="folders.length" class="mech-pages__rail">
|
|
31
43
|
<button
|
|
@@ -36,7 +48,7 @@
|
|
|
36
48
|
>
|
|
37
49
|
<VIcon name="book" />
|
|
38
50
|
<span class="mech-pages__rail-name">All pages</span>
|
|
39
|
-
<span class="mech-pages__rail-count">{{
|
|
51
|
+
<span class="mech-pages__rail-count">{{ listedPages.length }}</span>
|
|
40
52
|
</button>
|
|
41
53
|
<button
|
|
42
54
|
v-for="folder in folders"
|
|
@@ -146,6 +158,7 @@
|
|
|
146
158
|
<script setup lang="ts">
|
|
147
159
|
import { computed, inject, nextTick, onMounted, reactive, ref, useTemplateRef, watch } from 'vue'
|
|
148
160
|
import VIcon from '../components/VIcon.vue'
|
|
161
|
+
import VSegmented from '../components/VSegmented.vue'
|
|
149
162
|
import ConfirmDialog from './ConfirmDialog.vue'
|
|
150
163
|
import PageFormDialog from './PageFormDialog.vue'
|
|
151
164
|
import { useDialog } from '../ui/dialog'
|
|
@@ -178,9 +191,45 @@ const listEl = useTemplateRef<HTMLDivElement>('listEl')
|
|
|
178
191
|
|
|
179
192
|
const searching = computed(() => !!query.value.trim())
|
|
180
193
|
|
|
194
|
+
// Generated pages (plugin `generatePages`) can be numerous — a big catalog or
|
|
195
|
+
// API-doc set. This toggle hides them from the list; the preference persists per
|
|
196
|
+
// dev-server origin and defaults to shown. `listedPages` is the filtered set that
|
|
197
|
+
// every count, folder, and row below derives from.
|
|
198
|
+
const SHOW_GENERATED_KEY = 'mech:pages:show-generated'
|
|
199
|
+
const showGenerated = ref(typeof localStorage === 'undefined' || localStorage.getItem(SHOW_GENERATED_KEY) !== '0')
|
|
200
|
+
watch(showGenerated, (value) => {
|
|
201
|
+
try {
|
|
202
|
+
localStorage.setItem(SHOW_GENERATED_KEY, value ? '1' : '0')
|
|
203
|
+
} catch {
|
|
204
|
+
/* storage unavailable */
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
const hasGenerated = computed(() => pages.value.some((page) => page.generated))
|
|
208
|
+
const listedPages = computed(() =>
|
|
209
|
+
showGenerated.value ? pages.value : pages.value.filter((page) => !page.generated),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// Sort order within each folder group. Defaults to `path` (the server's own
|
|
213
|
+
// order — the folder rail is path-shaped), persisted like `showGenerated`.
|
|
214
|
+
const SORT_KEY = 'mech:pages:sort'
|
|
215
|
+
const sortKey = ref<'path' | 'name'>(
|
|
216
|
+
(typeof localStorage !== 'undefined' && localStorage.getItem(SORT_KEY)) === 'name' ? 'name' : 'path',
|
|
217
|
+
)
|
|
218
|
+
watch(sortKey, (value) => {
|
|
219
|
+
try {
|
|
220
|
+
localStorage.setItem(SORT_KEY, value)
|
|
221
|
+
} catch {
|
|
222
|
+
/* storage unavailable */
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
const sortOptions = [
|
|
226
|
+
{ value: 'path', label: 'Path' },
|
|
227
|
+
{ value: 'name', label: 'Name' },
|
|
228
|
+
]
|
|
229
|
+
|
|
181
230
|
const folders = computed(() => {
|
|
182
231
|
const counts = new Map<string, number>()
|
|
183
|
-
for (const page of
|
|
232
|
+
for (const page of listedPages.value) {
|
|
184
233
|
if (page.folderPath) counts.set(page.folderPath, (counts.get(page.folderPath) ?? 0) + 1)
|
|
185
234
|
}
|
|
186
235
|
return [...counts.entries()]
|
|
@@ -190,11 +239,19 @@ const folders = computed(() => {
|
|
|
190
239
|
|
|
191
240
|
// Search is global — it ignores the folder filter so a match can never hide.
|
|
192
241
|
const visible = computed(() => {
|
|
193
|
-
if (searching.value) return filterPages(
|
|
194
|
-
if (activeFolder.value) return
|
|
195
|
-
return
|
|
242
|
+
if (searching.value) return filterPages(listedPages.value, query.value)
|
|
243
|
+
if (activeFolder.value) return listedPages.value.filter((page) => page.folderPath === activeFolder.value)
|
|
244
|
+
return listedPages.value
|
|
196
245
|
})
|
|
197
|
-
|
|
246
|
+
// Sort within each folder group by the chosen key (grouping is preserved).
|
|
247
|
+
const sorted = computed(() =>
|
|
248
|
+
[...visible.value].sort((a, b) =>
|
|
249
|
+
sortKey.value === 'name'
|
|
250
|
+
? (a.name || a.path).localeCompare(b.name || b.path)
|
|
251
|
+
: a.path.localeCompare(b.path),
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
const groups = computed(() => groupPagesByFolder(sorted.value))
|
|
198
255
|
/** Flat list in render order — keyboard navigation walks this. */
|
|
199
256
|
const rows = computed(() => groups.value.flatMap((group) => group.pages))
|
|
200
257
|
// Inside a single folder the rail already names it; headers would be noise.
|
|
@@ -208,7 +265,9 @@ const monogram = computed(() =>
|
|
|
208
265
|
onMounted(async () => {
|
|
209
266
|
await load()
|
|
210
267
|
searchInput.value?.focus()
|
|
211
|
-
|
|
268
|
+
// Highlight the current page when it's in the (possibly filtered) list, else
|
|
269
|
+
// the first row — a generated current page hidden by the toggle falls back.
|
|
270
|
+
activePath.value = rows.value.some((page) => page.path === current.value)
|
|
212
271
|
? current.value
|
|
213
272
|
: (rows.value[0]?.path ?? null)
|
|
214
273
|
})
|
|
@@ -447,13 +506,39 @@ async function toggleDraft(page: PageItem) {
|
|
|
447
506
|
.mech-pages__toolbar {
|
|
448
507
|
display: flex;
|
|
449
508
|
gap: 8px;
|
|
450
|
-
padding: 0 24px
|
|
509
|
+
padding: 0 24px 12px;
|
|
451
510
|
flex: none;
|
|
452
511
|
}
|
|
453
512
|
.mech-pages__search-wrap {
|
|
454
513
|
position: relative;
|
|
455
514
|
flex: 1;
|
|
456
515
|
}
|
|
516
|
+
|
|
517
|
+
// ── Filter / sort row ────────────────────────────────────────────────────────
|
|
518
|
+
.mech-pages__filters {
|
|
519
|
+
display: flex;
|
|
520
|
+
align-items: center;
|
|
521
|
+
justify-content: space-between;
|
|
522
|
+
gap: 12px;
|
|
523
|
+
padding: 0 24px 14px;
|
|
524
|
+
flex: none;
|
|
525
|
+
}
|
|
526
|
+
.mech-pages__sort {
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
gap: 9px;
|
|
530
|
+
}
|
|
531
|
+
.mech-pages__sort-label {
|
|
532
|
+
font-size: 10.5px;
|
|
533
|
+
font-weight: 600;
|
|
534
|
+
text-transform: uppercase;
|
|
535
|
+
letter-spacing: 0.05em;
|
|
536
|
+
color: var(--mech-muted);
|
|
537
|
+
}
|
|
538
|
+
.mech-pages__toggle {
|
|
539
|
+
flex: none;
|
|
540
|
+
white-space: nowrap;
|
|
541
|
+
}
|
|
457
542
|
.mech-pages__search-icon {
|
|
458
543
|
position: absolute;
|
|
459
544
|
left: 12px;
|
package/src/vite/entries.ts
CHANGED
|
@@ -150,6 +150,9 @@ export interface SsrEntryOptions {
|
|
|
150
150
|
/** Site identity (origin, name) from the plugin options — rides the bundle so
|
|
151
151
|
* `mechanica export` can emit canonical URLs, sitemap.xml and robots.txt. */
|
|
152
152
|
site?: { url?: string; name?: string }
|
|
153
|
+
/** CDN base URL for static assets (plugin `assetsUrl`) — rides the bundle so
|
|
154
|
+
* `mechanica export` can serve `/assets/…` and `/media/…` from it. */
|
|
155
|
+
assetsUrl?: string
|
|
153
156
|
/** The site's locale config (multi-language) — rides the bundle so the export
|
|
154
157
|
* can walk translations, and gets baked into each page's `state.locales`. */
|
|
155
158
|
locales?: import('mechanica-shared').LocalesConfig | null
|
|
@@ -178,6 +181,7 @@ export function generateSsrEntry(options: SsrEntryOptions): string {
|
|
|
178
181
|
``,
|
|
179
182
|
`registerComponents(blocksMap)`,
|
|
180
183
|
`export const site = ${JSON.stringify(options.site ?? {})}`,
|
|
184
|
+
`export const assetsUrl = ${JSON.stringify(options.assetsUrl ?? null)}`,
|
|
181
185
|
`export const locales = ${JSON.stringify(options.locales ?? null)}`,
|
|
182
186
|
`export const generatedPages = ${JSON.stringify(options.generatedPages ?? [])}`,
|
|
183
187
|
`export const dataEntries = getDataEntries()`,
|
package/src/vite/plugin.ts
CHANGED
|
@@ -92,6 +92,21 @@ export interface MechanicaPluginOptions {
|
|
|
92
92
|
siteUrl?: string
|
|
93
93
|
/** The site's display name — `{{ site.name }}` + WebSite JSON-LD at export. */
|
|
94
94
|
siteName?: string
|
|
95
|
+
/**
|
|
96
|
+
* Base URL to serve static assets from at export — a CDN origin like
|
|
97
|
+
* `https://cdn.example.com`. When set, `mechanica export` rewrites both the
|
|
98
|
+
* build assets (`/assets/…`) and uploaded media (`/media/…`) to
|
|
99
|
+
* `<assetsUrl>/assets/…` and `<assetsUrl>/media/…`. Files are still emitted
|
|
100
|
+
* into `export/` — upload them to the CDN yourself (e.g. in CI). A trailing
|
|
101
|
+
* slash is ignored. `mechanica export --assets-url <origin>` overrides it.
|
|
102
|
+
*
|
|
103
|
+
* Only affects the static export; the dev server keeps serving assets
|
|
104
|
+
* locally. For build assets referenced from inside CSS/JS chunks (fonts,
|
|
105
|
+
* `url(...)` backgrounds) on a cross-origin CDN, set Vite's `base` instead —
|
|
106
|
+
* this option rewrites HTML asset tags and uploaded media, not chunk-internal
|
|
107
|
+
* references.
|
|
108
|
+
*/
|
|
109
|
+
assetsUrl?: string
|
|
95
110
|
/**
|
|
96
111
|
* Multi-language configuration. Either a full config
|
|
97
112
|
* (`{ default: 'en', all: ['en', 'ru'], labels? }`) or a bare list of codes
|
|
@@ -465,6 +480,7 @@ export function mechanica(options: MechanicaPluginOptions = {}): Plugin {
|
|
|
465
480
|
return generateSsrEntry({
|
|
466
481
|
userEntry,
|
|
467
482
|
site: { url: options.siteUrl, name: options.siteName },
|
|
483
|
+
assetsUrl: options.assetsUrl,
|
|
468
484
|
locales,
|
|
469
485
|
generatedPages: await loadGeneratedPages(),
|
|
470
486
|
})
|