apostrophe 4.28.1 → 4.29.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/CHANGELOG.md +29 -4
- package/README.md +2 -2
- package/defaults.js +1 -0
- package/lib/safe-json-script.js +27 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +3 -5
- package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +13 -1
- package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
- package/modules/@apostrophecms/attachment/index.js +43 -1
- package/modules/@apostrophecms/color-field/index.js +7 -1
- package/modules/@apostrophecms/doc/index.js +11 -1
- package/modules/@apostrophecms/doc-type/index.js +165 -32
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -1
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +104 -59
- package/modules/@apostrophecms/file/index.js +109 -8
- package/modules/@apostrophecms/i18n/i18n/de.json +0 -2
- package/modules/@apostrophecms/i18n/i18n/en.json +40 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/it.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +0 -1
- package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nBatchReporting.js +18 -1
- package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nLocalizeActions.js +50 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +56 -13
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +8 -2
- package/modules/@apostrophecms/layout-column-widget/index.js +156 -163
- package/modules/@apostrophecms/layout-widget/index.js +7 -2
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +6 -11
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +3 -5
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridLayout.vue +4 -4
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -16
- package/modules/@apostrophecms/layout-widget/ui/apos/lib/grid-state.mjs +7 -27
- package/modules/@apostrophecms/layout-widget/views/column.html +7 -9
- package/modules/@apostrophecms/login/index.js +39 -40
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +17 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +3 -2
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -0
- package/modules/@apostrophecms/page/index.js +2 -0
- package/modules/@apostrophecms/piece-type/index.js +3 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +5 -0
- package/modules/@apostrophecms/recently-edited/index.js +831 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +54 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedCombo.vue +454 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilterTag.vue +75 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilters.vue +287 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedIcon.vue +16 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedManager.vue +346 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedBatch.js +193 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedData.js +276 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFetch.js +199 -0
- package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFilters.js +100 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +8 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +1 -1
- package/modules/@apostrophecms/styles/index.js +10 -0
- package/modules/@apostrophecms/styles/lib/apiRoutes.js +6 -0
- package/modules/@apostrophecms/styles/lib/handlers.js +5 -0
- package/modules/@apostrophecms/styles/lib/methods.js +9 -3
- package/modules/@apostrophecms/styles/lib/presets.js +119 -0
- package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +3 -8
- package/modules/@apostrophecms/styles/ui/apos/composables/AposStyles.js +1 -3
- package/modules/@apostrophecms/styles/ui/apos/render-factory.js +29 -0
- package/modules/@apostrophecms/styles/ui/apos/universal/backgroundHelpers.mjs +140 -0
- package/modules/@apostrophecms/styles/ui/apos/universal/customRules.mjs +105 -0
- package/modules/@apostrophecms/styles/ui/apos/universal/render.mjs +195 -15
- package/modules/@apostrophecms/template/index.js +22 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +2 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +18 -4
- package/modules/@apostrophecms/ui/ui/apos/composables/useInfiniteScroll.js +91 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +5 -2
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
- package/modules/@apostrophecms/url/index.js +38 -4
- package/modules/@apostrophecms/widget-type/index.js +22 -6
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +8 -4
- package/package.json +17 -17
- package/test/add-missing-schema-fields-project/node_modules/.package-lock.json +2 -2
- package/test/layout-widget-migration.js +719 -0
- package/test/login-requirements.js +1 -1
- package/test/pieces-public-api.js +80 -0
- package/test/pieces.js +25 -0
- package/test/recently-edited.js +2311 -0
- package/test/schemas.js +39 -3
- package/test/static-build.js +642 -0
- package/test/styles.js +2569 -0
- package/.claude/settings.local.json +0 -15
- package/modules/@apostrophecms/layout-widget/ui/apos/components/AposLayoutColControlDialog.vue +0 -171
package/test/static-build.js
CHANGED
|
@@ -2698,4 +2698,646 @@ describe('Static Build Support', function () {
|
|
|
2698
2698
|
});
|
|
2699
2699
|
});
|
|
2700
2700
|
});
|
|
2701
|
+
|
|
2702
|
+
describe('getBaseUrl with relative option', function () {
|
|
2703
|
+
|
|
2704
|
+
describe('no prefix configured', function () {
|
|
2705
|
+
let apos;
|
|
2706
|
+
|
|
2707
|
+
before(async function () {
|
|
2708
|
+
apos = await t.create({
|
|
2709
|
+
root: module,
|
|
2710
|
+
baseUrl: 'http://localhost:3000',
|
|
2711
|
+
staticBaseUrl: 'https://www.example.com',
|
|
2712
|
+
modules: {
|
|
2713
|
+
'@apostrophecms/url': {
|
|
2714
|
+
options: { static: true }
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
after(async function () {
|
|
2721
|
+
await t.destroy(apos);
|
|
2722
|
+
apos = null;
|
|
2723
|
+
});
|
|
2724
|
+
|
|
2725
|
+
it('returns empty string when relative is true and no prefix', function () {
|
|
2726
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2727
|
+
const result = apos.url.getBaseUrl(req, { relative: true });
|
|
2728
|
+
assert.strictEqual(result, '');
|
|
2729
|
+
});
|
|
2730
|
+
|
|
2731
|
+
it('returns origin-based URL when relative is false', function () {
|
|
2732
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2733
|
+
const result = apos.url.getBaseUrl(req, { relative: false });
|
|
2734
|
+
assert.strictEqual(result, 'http://localhost:3000');
|
|
2735
|
+
});
|
|
2736
|
+
|
|
2737
|
+
it('relative ignores staticBaseUrl', function () {
|
|
2738
|
+
const req = apos.task.getAnonReq({
|
|
2739
|
+
mode: 'published',
|
|
2740
|
+
staticBuild: true
|
|
2741
|
+
});
|
|
2742
|
+
const result = apos.url.getBaseUrl(req, { relative: true });
|
|
2743
|
+
assert.strictEqual(result, '');
|
|
2744
|
+
});
|
|
2745
|
+
});
|
|
2746
|
+
|
|
2747
|
+
describe('with prefix configured', function () {
|
|
2748
|
+
let apos;
|
|
2749
|
+
|
|
2750
|
+
before(async function () {
|
|
2751
|
+
apos = await t.create({
|
|
2752
|
+
root: module,
|
|
2753
|
+
baseUrl: 'http://localhost:3000',
|
|
2754
|
+
prefix: '/cms',
|
|
2755
|
+
modules: {
|
|
2756
|
+
'@apostrophecms/url': {
|
|
2757
|
+
options: { static: true }
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
});
|
|
2761
|
+
});
|
|
2762
|
+
|
|
2763
|
+
after(async function () {
|
|
2764
|
+
await t.destroy(apos);
|
|
2765
|
+
apos = null;
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
it('returns prefix when relative is true', function () {
|
|
2769
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2770
|
+
const result = apos.url.getBaseUrl(req, { relative: true });
|
|
2771
|
+
assert.strictEqual(result, '/cms');
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2774
|
+
it('returns origin + prefix when relative is false', function () {
|
|
2775
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2776
|
+
const result = apos.url.getBaseUrl(req, { relative: false });
|
|
2777
|
+
assert.strictEqual(result, 'http://localhost:3000/cms');
|
|
2778
|
+
});
|
|
2779
|
+
});
|
|
2780
|
+
});
|
|
2781
|
+
|
|
2782
|
+
describe('Pretty URL file methods', function () {
|
|
2783
|
+
|
|
2784
|
+
describe('prettyUrls disabled (default)', function () {
|
|
2785
|
+
let apos;
|
|
2786
|
+
|
|
2787
|
+
before(async function () {
|
|
2788
|
+
apos = await t.create({
|
|
2789
|
+
root: module,
|
|
2790
|
+
modules: {
|
|
2791
|
+
'@apostrophecms/url': {
|
|
2792
|
+
options: { static: true }
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
});
|
|
2797
|
+
|
|
2798
|
+
after(async function () {
|
|
2799
|
+
await t.destroy(apos);
|
|
2800
|
+
apos = null;
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
it('getPrettyUrlBase returns null', function () {
|
|
2804
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2805
|
+
assert.strictEqual(apos.file.getPrettyUrlBase(req), null);
|
|
2806
|
+
});
|
|
2807
|
+
|
|
2808
|
+
it('getPrettyPath returns null', function () {
|
|
2809
|
+
assert.strictEqual(
|
|
2810
|
+
apos.file.getPrettyPath({
|
|
2811
|
+
slug: 'file-test',
|
|
2812
|
+
attachment: { extension: 'pdf' }
|
|
2813
|
+
}),
|
|
2814
|
+
null
|
|
2815
|
+
);
|
|
2816
|
+
});
|
|
2817
|
+
|
|
2818
|
+
it('applyPrettyUrlPaths is a no-op', async function () {
|
|
2819
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2820
|
+
const meta = {
|
|
2821
|
+
uploadsUrl: '/uploads',
|
|
2822
|
+
results: [
|
|
2823
|
+
{
|
|
2824
|
+
_id: 'att-1',
|
|
2825
|
+
urls: [ { path: '/attachments/original.pdf' } ]
|
|
2826
|
+
}
|
|
2827
|
+
]
|
|
2828
|
+
};
|
|
2829
|
+
await apos.file.applyPrettyUrlPaths(req, meta);
|
|
2830
|
+
// Should remain unchanged
|
|
2831
|
+
assert.strictEqual(meta.results[0].urls[0].path, '/attachments/original.pdf');
|
|
2832
|
+
assert.strictEqual(meta.results[0].base, undefined);
|
|
2833
|
+
});
|
|
2834
|
+
});
|
|
2835
|
+
|
|
2836
|
+
describe('prettyUrls enabled, no prefix', function () {
|
|
2837
|
+
let apos;
|
|
2838
|
+
|
|
2839
|
+
before(async function () {
|
|
2840
|
+
apos = await t.create({
|
|
2841
|
+
root: module,
|
|
2842
|
+
baseUrl: 'http://localhost:3000',
|
|
2843
|
+
modules: {
|
|
2844
|
+
'@apostrophecms/url': {
|
|
2845
|
+
options: { static: true }
|
|
2846
|
+
},
|
|
2847
|
+
'@apostrophecms/file': {
|
|
2848
|
+
options: {
|
|
2849
|
+
prettyUrls: true
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
});
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
after(async function () {
|
|
2857
|
+
await t.destroy(apos);
|
|
2858
|
+
apos = null;
|
|
2859
|
+
});
|
|
2860
|
+
|
|
2861
|
+
it('getPrettyUrlBase returns base with prettyUrlDir', function () {
|
|
2862
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2863
|
+
assert.strictEqual(
|
|
2864
|
+
apos.file.getPrettyUrlBase(req),
|
|
2865
|
+
'http://localhost:3000/files'
|
|
2866
|
+
);
|
|
2867
|
+
});
|
|
2868
|
+
|
|
2869
|
+
it('getPrettyUrlBase with relative returns just prettyUrlDir', function () {
|
|
2870
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2871
|
+
assert.strictEqual(
|
|
2872
|
+
apos.file.getPrettyUrlBase(req, { relative: true }),
|
|
2873
|
+
'/files'
|
|
2874
|
+
);
|
|
2875
|
+
});
|
|
2876
|
+
|
|
2877
|
+
it('getPrettyPath returns slug-based path', function () {
|
|
2878
|
+
const doc = {
|
|
2879
|
+
slug: 'file-my-document',
|
|
2880
|
+
attachment: { extension: 'pdf' }
|
|
2881
|
+
};
|
|
2882
|
+
assert.strictEqual(apos.file.getPrettyPath(doc), '/my-document.pdf');
|
|
2883
|
+
});
|
|
2884
|
+
|
|
2885
|
+
it('getPrettyPath strips slugPrefix', function () {
|
|
2886
|
+
const doc = {
|
|
2887
|
+
slug: 'file-design-principles',
|
|
2888
|
+
attachment: { extension: 'pdf' }
|
|
2889
|
+
};
|
|
2890
|
+
assert.strictEqual(apos.file.getPrettyPath(doc), '/design-principles.pdf');
|
|
2891
|
+
});
|
|
2892
|
+
|
|
2893
|
+
it('getPrettyPath returns null when no attachment', function () {
|
|
2894
|
+
assert.strictEqual(apos.file.getPrettyPath({ slug: 'file-test' }), null);
|
|
2895
|
+
});
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2898
|
+
describe('prettyUrls enabled with prefix', function () {
|
|
2899
|
+
let apos;
|
|
2900
|
+
|
|
2901
|
+
before(async function () {
|
|
2902
|
+
apos = await t.create({
|
|
2903
|
+
root: module,
|
|
2904
|
+
baseUrl: 'http://localhost:3000',
|
|
2905
|
+
prefix: '/cms',
|
|
2906
|
+
modules: {
|
|
2907
|
+
'@apostrophecms/url': {
|
|
2908
|
+
options: { static: true }
|
|
2909
|
+
},
|
|
2910
|
+
'@apostrophecms/file': {
|
|
2911
|
+
options: {
|
|
2912
|
+
prettyUrls: true
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
});
|
|
2918
|
+
|
|
2919
|
+
after(async function () {
|
|
2920
|
+
await t.destroy(apos);
|
|
2921
|
+
apos = null;
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2924
|
+
it('getPrettyUrlBase includes prefix', function () {
|
|
2925
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2926
|
+
assert.strictEqual(
|
|
2927
|
+
apos.file.getPrettyUrlBase(req),
|
|
2928
|
+
'http://localhost:3000/cms/files'
|
|
2929
|
+
);
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
it('getPrettyUrlBase with relative includes prefix', function () {
|
|
2933
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
2934
|
+
assert.strictEqual(
|
|
2935
|
+
apos.file.getPrettyUrlBase(req, { relative: true }),
|
|
2936
|
+
'/cms/files'
|
|
2937
|
+
);
|
|
2938
|
+
});
|
|
2939
|
+
});
|
|
2940
|
+
});
|
|
2941
|
+
|
|
2942
|
+
describe('applyPrettyUrlPaths', function () {
|
|
2943
|
+
let apos;
|
|
2944
|
+
|
|
2945
|
+
before(async function () {
|
|
2946
|
+
apos = await t.create({
|
|
2947
|
+
root: module,
|
|
2948
|
+
modules: {
|
|
2949
|
+
'@apostrophecms/url': {
|
|
2950
|
+
options: { static: true }
|
|
2951
|
+
},
|
|
2952
|
+
'@apostrophecms/file': {
|
|
2953
|
+
options: {
|
|
2954
|
+
prettyUrls: true
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
});
|
|
2959
|
+
|
|
2960
|
+
const req = apos.task.getReq();
|
|
2961
|
+
|
|
2962
|
+
// Insert a file piece with a known attachment.
|
|
2963
|
+
// We seed the attachment directly in the DB to avoid
|
|
2964
|
+
// needing real uploaded files.
|
|
2965
|
+
const file = await apos.file.insert(req, {
|
|
2966
|
+
title: 'Design Principles',
|
|
2967
|
+
visibility: 'public',
|
|
2968
|
+
attachment: {
|
|
2969
|
+
_id: 'att-pretty-1',
|
|
2970
|
+
name: 'design-principles',
|
|
2971
|
+
extension: 'pdf',
|
|
2972
|
+
group: 'office',
|
|
2973
|
+
type: 'attachment'
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
|
|
2977
|
+
// Also seed the attachment record so getStaticMetadata
|
|
2978
|
+
// can find it.
|
|
2979
|
+
await apos.attachment.db.insertOne({
|
|
2980
|
+
_id: 'att-pretty-1',
|
|
2981
|
+
name: 'design-principles',
|
|
2982
|
+
extension: 'pdf',
|
|
2983
|
+
group: 'office',
|
|
2984
|
+
archived: false,
|
|
2985
|
+
docIds: [ `${file.aposDocId}:en:published` ],
|
|
2986
|
+
crops: [],
|
|
2987
|
+
used: true,
|
|
2988
|
+
utilized: true
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2991
|
+
// Insert a second file.
|
|
2992
|
+
const file2 = await apos.file.insert(req, {
|
|
2993
|
+
title: 'Annual Report',
|
|
2994
|
+
visibility: 'public',
|
|
2995
|
+
attachment: {
|
|
2996
|
+
_id: 'att-pretty-2',
|
|
2997
|
+
name: 'annual-report',
|
|
2998
|
+
extension: 'pdf',
|
|
2999
|
+
group: 'office',
|
|
3000
|
+
type: 'attachment'
|
|
3001
|
+
}
|
|
3002
|
+
});
|
|
3003
|
+
|
|
3004
|
+
await apos.attachment.db.insertOne({
|
|
3005
|
+
_id: 'att-pretty-2',
|
|
3006
|
+
name: 'annual-report',
|
|
3007
|
+
extension: 'pdf',
|
|
3008
|
+
group: 'office',
|
|
3009
|
+
archived: false,
|
|
3010
|
+
docIds: [ `${file2.aposDocId}:en:published` ],
|
|
3011
|
+
crops: [],
|
|
3012
|
+
used: true,
|
|
3013
|
+
utilized: true
|
|
3014
|
+
});
|
|
3015
|
+
|
|
3016
|
+
// Seed a non-file attachment (image) that should NOT
|
|
3017
|
+
// be touched by applyPrettyUrlPaths.
|
|
3018
|
+
await apos.attachment.db.insertOne({
|
|
3019
|
+
_id: 'att-image-notouch',
|
|
3020
|
+
name: 'photo',
|
|
3021
|
+
extension: 'jpg',
|
|
3022
|
+
group: 'images',
|
|
3023
|
+
width: 800,
|
|
3024
|
+
height: 600,
|
|
3025
|
+
archived: false,
|
|
3026
|
+
docIds: [ 'some-img:en:published' ],
|
|
3027
|
+
crops: [],
|
|
3028
|
+
used: true,
|
|
3029
|
+
utilized: true
|
|
3030
|
+
});
|
|
3031
|
+
});
|
|
3032
|
+
|
|
3033
|
+
after(async function () {
|
|
3034
|
+
await t.destroy(apos);
|
|
3035
|
+
apos = null;
|
|
3036
|
+
});
|
|
3037
|
+
|
|
3038
|
+
it('mutates matching entries with base and pretty urls', async function () {
|
|
3039
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3040
|
+
const meta = {
|
|
3041
|
+
uploadsUrl: '/uploads',
|
|
3042
|
+
results: [
|
|
3043
|
+
{
|
|
3044
|
+
_id: 'att-pretty-1',
|
|
3045
|
+
urls: [ { path: '/attachments/design-principles.pdf' } ]
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
_id: 'att-image-notouch',
|
|
3049
|
+
urls: [
|
|
3050
|
+
{
|
|
3051
|
+
size: 'full',
|
|
3052
|
+
path: '/attachments/photo-full.jpg'
|
|
3053
|
+
},
|
|
3054
|
+
{
|
|
3055
|
+
size: 'one-half',
|
|
3056
|
+
path: '/attachments/photo-one-half.jpg'
|
|
3057
|
+
}
|
|
3058
|
+
]
|
|
3059
|
+
},
|
|
3060
|
+
{
|
|
3061
|
+
_id: 'att-pretty-2',
|
|
3062
|
+
urls: [ { path: '/attachments/annual-report.pdf' } ]
|
|
3063
|
+
}
|
|
3064
|
+
]
|
|
3065
|
+
};
|
|
3066
|
+
|
|
3067
|
+
await apos.file.applyPrettyUrlPaths(req, meta);
|
|
3068
|
+
|
|
3069
|
+
// File attachment should be mutated
|
|
3070
|
+
const pretty1 = meta.results.find(a => a._id === 'att-pretty-1');
|
|
3071
|
+
assert.strictEqual(pretty1.base, '/files');
|
|
3072
|
+
assert.strictEqual(pretty1.urls.length, 1);
|
|
3073
|
+
assert.strictEqual(pretty1.urls[0].path, '/design-principles.pdf');
|
|
3074
|
+
assert.strictEqual(pretty1.urls[0].size, undefined);
|
|
3075
|
+
|
|
3076
|
+
// Second file attachment should also be mutated
|
|
3077
|
+
const pretty2 = meta.results.find(a => a._id === 'att-pretty-2');
|
|
3078
|
+
assert.strictEqual(pretty2.base, '/files');
|
|
3079
|
+
assert.strictEqual(pretty2.urls.length, 1);
|
|
3080
|
+
assert.strictEqual(pretty2.urls[0].path, '/annual-report.pdf');
|
|
3081
|
+
|
|
3082
|
+
// Image attachment should be untouched
|
|
3083
|
+
const img = meta.results.find(a => a._id === 'att-image-notouch');
|
|
3084
|
+
assert.strictEqual(img.base, undefined);
|
|
3085
|
+
assert.strictEqual(img.urls.length, 2);
|
|
3086
|
+
assert.strictEqual(img.urls[0].size, 'full');
|
|
3087
|
+
});
|
|
3088
|
+
|
|
3089
|
+
it('handles empty results gracefully', async function () {
|
|
3090
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3091
|
+
const meta = {
|
|
3092
|
+
uploadsUrl: '/uploads',
|
|
3093
|
+
results: []
|
|
3094
|
+
};
|
|
3095
|
+
await apos.file.applyPrettyUrlPaths(req, meta);
|
|
3096
|
+
assert.strictEqual(meta.results.length, 0);
|
|
3097
|
+
});
|
|
3098
|
+
|
|
3099
|
+
it('handles null attachmentMeta gracefully', async function () {
|
|
3100
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3101
|
+
// Should not throw
|
|
3102
|
+
await apos.file.applyPrettyUrlPaths(req, null);
|
|
3103
|
+
});
|
|
3104
|
+
|
|
3105
|
+
it('does not touch entries whose _id has no matching file doc', async function () {
|
|
3106
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3107
|
+
const meta = {
|
|
3108
|
+
uploadsUrl: '/uploads',
|
|
3109
|
+
results: [
|
|
3110
|
+
{
|
|
3111
|
+
_id: 'att-nonexistent',
|
|
3112
|
+
urls: [ { path: '/attachments/ghost.pdf' } ]
|
|
3113
|
+
}
|
|
3114
|
+
]
|
|
3115
|
+
};
|
|
3116
|
+
await apos.file.applyPrettyUrlPaths(req, meta);
|
|
3117
|
+
const entry = meta.results[0];
|
|
3118
|
+
assert.strictEqual(entry.base, undefined);
|
|
3119
|
+
assert.strictEqual(entry.urls[0].path, '/attachments/ghost.pdf');
|
|
3120
|
+
});
|
|
3121
|
+
});
|
|
3122
|
+
|
|
3123
|
+
describe('getAllUrlMetadata with prettyUrls', function () {
|
|
3124
|
+
let apos;
|
|
3125
|
+
|
|
3126
|
+
before(async function () {
|
|
3127
|
+
apos = await t.create({
|
|
3128
|
+
root: module,
|
|
3129
|
+
modules: {
|
|
3130
|
+
'@apostrophecms/url': {
|
|
3131
|
+
options: { static: true }
|
|
3132
|
+
},
|
|
3133
|
+
'@apostrophecms/file': {
|
|
3134
|
+
options: {
|
|
3135
|
+
prettyUrls: true
|
|
3136
|
+
}
|
|
3137
|
+
},
|
|
3138
|
+
article: {
|
|
3139
|
+
extend: '@apostrophecms/piece-type',
|
|
3140
|
+
options: {
|
|
3141
|
+
name: 'article',
|
|
3142
|
+
label: 'Article',
|
|
3143
|
+
alias: 'article'
|
|
3144
|
+
},
|
|
3145
|
+
fields: {
|
|
3146
|
+
add: {
|
|
3147
|
+
_file: {
|
|
3148
|
+
type: 'relationship',
|
|
3149
|
+
withType: '@apostrophecms/file',
|
|
3150
|
+
label: 'File',
|
|
3151
|
+
max: 1
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
},
|
|
3156
|
+
'article-page': {
|
|
3157
|
+
extend: '@apostrophecms/piece-page-type',
|
|
3158
|
+
options: {
|
|
3159
|
+
name: 'articlePage',
|
|
3160
|
+
label: 'Articles',
|
|
3161
|
+
alias: 'articlePage',
|
|
3162
|
+
perPage: 10
|
|
3163
|
+
}
|
|
3164
|
+
},
|
|
3165
|
+
'@apostrophecms/page': {
|
|
3166
|
+
options: {
|
|
3167
|
+
park: [
|
|
3168
|
+
{
|
|
3169
|
+
title: 'Articles',
|
|
3170
|
+
type: 'articlePage',
|
|
3171
|
+
slug: '/articles',
|
|
3172
|
+
parkedId: 'articles'
|
|
3173
|
+
}
|
|
3174
|
+
]
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3180
|
+
const req = apos.task.getReq();
|
|
3181
|
+
|
|
3182
|
+
// Insert a file piece
|
|
3183
|
+
const file = await apos.file.insert(req, {
|
|
3184
|
+
title: 'Spec Document',
|
|
3185
|
+
visibility: 'public',
|
|
3186
|
+
attachment: {
|
|
3187
|
+
_id: 'att-spec-pdf',
|
|
3188
|
+
name: 'spec-document',
|
|
3189
|
+
extension: 'pdf',
|
|
3190
|
+
group: 'office',
|
|
3191
|
+
type: 'attachment'
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
|
|
3195
|
+
// Seed attachment record
|
|
3196
|
+
await apos.attachment.db.insertOne({
|
|
3197
|
+
_id: 'att-spec-pdf',
|
|
3198
|
+
name: 'spec-document',
|
|
3199
|
+
extension: 'pdf',
|
|
3200
|
+
group: 'office',
|
|
3201
|
+
archived: false,
|
|
3202
|
+
docIds: [ `${file.aposDocId}:en:published` ],
|
|
3203
|
+
crops: [],
|
|
3204
|
+
used: true,
|
|
3205
|
+
utilized: true
|
|
3206
|
+
});
|
|
3207
|
+
|
|
3208
|
+
// Insert an article referencing the file
|
|
3209
|
+
const article = await apos.article.insert(req, {
|
|
3210
|
+
title: 'Article With File',
|
|
3211
|
+
visibility: 'public'
|
|
3212
|
+
});
|
|
3213
|
+
|
|
3214
|
+
// Wire the relationship via idsStorage
|
|
3215
|
+
await apos.doc.db.updateMany(
|
|
3216
|
+
{ aposDocId: article.aposDocId },
|
|
3217
|
+
{ $set: { fileIds: [ file.aposDocId ] } }
|
|
3218
|
+
);
|
|
3219
|
+
});
|
|
3220
|
+
|
|
3221
|
+
after(async function () {
|
|
3222
|
+
await t.destroy(apos);
|
|
3223
|
+
apos = null;
|
|
3224
|
+
});
|
|
3225
|
+
|
|
3226
|
+
it('attachment entries with pretty URLs should have base property', async function () {
|
|
3227
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3228
|
+
const result = await apos.url.getAllUrlMetadata(req, {
|
|
3229
|
+
attachments: { scope: 'used' }
|
|
3230
|
+
});
|
|
3231
|
+
assert(result.attachments);
|
|
3232
|
+
const pdfAtt = result.attachments.results.find(a => a._id === 'att-spec-pdf');
|
|
3233
|
+
assert(pdfAtt, 'Should find the pretty URL attachment');
|
|
3234
|
+
assert.strictEqual(pdfAtt.base, '/files');
|
|
3235
|
+
assert.strictEqual(pdfAtt.urls.length, 1);
|
|
3236
|
+
assert(
|
|
3237
|
+
pdfAtt.urls[0].path.endsWith('.pdf'),
|
|
3238
|
+
'Pretty URL path should end with .pdf'
|
|
3239
|
+
);
|
|
3240
|
+
assert(
|
|
3241
|
+
!pdfAtt.urls[0].path.includes('/attachments/'),
|
|
3242
|
+
'Pretty URL path should not contain /attachments/'
|
|
3243
|
+
);
|
|
3244
|
+
});
|
|
3245
|
+
|
|
3246
|
+
it('uploadsUrl is unchanged by applyPrettyUrlPaths', async function () {
|
|
3247
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3248
|
+
const result = await apos.url.getAllUrlMetadata(req, {
|
|
3249
|
+
attachments: { scope: 'used' }
|
|
3250
|
+
});
|
|
3251
|
+
assert(typeof result.attachments.uploadsUrl === 'string');
|
|
3252
|
+
assert(
|
|
3253
|
+
result.attachments.uploadsUrl.includes('/uploads'),
|
|
3254
|
+
'uploadsUrl should still reference /uploads'
|
|
3255
|
+
);
|
|
3256
|
+
});
|
|
3257
|
+
|
|
3258
|
+
it('non-file attachments should not have base property', async function () {
|
|
3259
|
+
// Seed a regular image attachment
|
|
3260
|
+
await apos.attachment.db.insertOne({
|
|
3261
|
+
_id: 'att-regular-img',
|
|
3262
|
+
name: 'regular-photo',
|
|
3263
|
+
extension: 'jpg',
|
|
3264
|
+
group: 'images',
|
|
3265
|
+
width: 400,
|
|
3266
|
+
height: 300,
|
|
3267
|
+
archived: false,
|
|
3268
|
+
docIds: [],
|
|
3269
|
+
crops: [],
|
|
3270
|
+
used: true,
|
|
3271
|
+
utilized: true
|
|
3272
|
+
});
|
|
3273
|
+
|
|
3274
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3275
|
+
const result = await apos.url.getAllUrlMetadata(req, {
|
|
3276
|
+
attachments: { scope: 'all' }
|
|
3277
|
+
});
|
|
3278
|
+
const imgAtt = result.attachments.results.find(a => a._id === 'att-regular-img');
|
|
3279
|
+
assert(imgAtt, 'Should find the regular image attachment');
|
|
3280
|
+
assert.strictEqual(imgAtt.base, undefined, 'Regular attachment should not have base');
|
|
3281
|
+
assert(imgAtt.urls.length > 1, 'Image should have multiple size variants');
|
|
3282
|
+
});
|
|
3283
|
+
});
|
|
3284
|
+
|
|
3285
|
+
describe('addUrls with prettyUrls', function () {
|
|
3286
|
+
let apos;
|
|
3287
|
+
|
|
3288
|
+
before(async function () {
|
|
3289
|
+
apos = await t.create({
|
|
3290
|
+
root: module,
|
|
3291
|
+
baseUrl: 'http://localhost:3000',
|
|
3292
|
+
modules: {
|
|
3293
|
+
'@apostrophecms/file': {
|
|
3294
|
+
options: {
|
|
3295
|
+
prettyUrls: true
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
});
|
|
3300
|
+
});
|
|
3301
|
+
|
|
3302
|
+
after(async function () {
|
|
3303
|
+
await t.destroy(apos);
|
|
3304
|
+
apos = null;
|
|
3305
|
+
});
|
|
3306
|
+
|
|
3307
|
+
it('sets _url to pretty URL on file docs', function () {
|
|
3308
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3309
|
+
const files = [
|
|
3310
|
+
{
|
|
3311
|
+
slug: 'file-my-document',
|
|
3312
|
+
attachment: { extension: 'pdf' }
|
|
3313
|
+
}
|
|
3314
|
+
];
|
|
3315
|
+
apos.file.addUrls(req, files);
|
|
3316
|
+
assert.strictEqual(files[0]._url, 'http://localhost:3000/files/my-document.pdf');
|
|
3317
|
+
});
|
|
3318
|
+
|
|
3319
|
+
it('sets attachment._prettyUrl', function () {
|
|
3320
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3321
|
+
const files = [
|
|
3322
|
+
{
|
|
3323
|
+
slug: 'file-report',
|
|
3324
|
+
attachment: { extension: 'pdf' }
|
|
3325
|
+
}
|
|
3326
|
+
];
|
|
3327
|
+
apos.file.addUrls(req, files);
|
|
3328
|
+
assert.strictEqual(files[0].attachment._prettyUrl, 'http://localhost:3000/files/report.pdf');
|
|
3329
|
+
});
|
|
3330
|
+
|
|
3331
|
+
it('uses relative URL when relative option is true', function () {
|
|
3332
|
+
const req = apos.task.getAnonReq({ mode: 'published' });
|
|
3333
|
+
const files = [
|
|
3334
|
+
{
|
|
3335
|
+
slug: 'file-guide',
|
|
3336
|
+
attachment: { extension: 'pdf' }
|
|
3337
|
+
}
|
|
3338
|
+
];
|
|
3339
|
+
apos.file.addUrls(req, files, { relative: true });
|
|
3340
|
+
assert.strictEqual(files[0]._url, '/files/guide.pdf');
|
|
3341
|
+
});
|
|
3342
|
+
});
|
|
2701
3343
|
});
|