forked from decaporg/decap-cms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstega.ts
More file actions
133 lines (121 loc) · 4.09 KB
/
Copy pathstega.ts
File metadata and controls
133 lines (121 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { vercelStegaEncode } from '@vercel/stega';
import { isImmutableMap, isImmutableList } from 'decap-cms-lib-util/src/types';
import type { Map as ImmutableMap, List } from 'immutable';
import type { CmsField } from 'decap-cms-core';
/**
* Context passed to encode functions, containing the current state of the encoding process
*/
interface EncodeContext {
fields: CmsField[]; // Available CMS fields at current level
path: string; // Path to current value in object tree
visit: (value: unknown, fields: CmsField[], path: string) => unknown; // Visitor for recursive traversal
}
/**
* Get the fields that should be used for encoding nested values
*/
function getNestedFields(f?: CmsField): CmsField[] {
if (f) {
if ('types' in f) {
return f.types ?? [];
}
if ('fields' in f) {
return f.fields ?? [];
}
if ('field' in f) {
return f.field ? [f.field] : [];
}
return [f];
}
return [];
}
/**
* Encode a string value by appending steganographic data
* For markdown fields, encode each paragraph separately
*/
function encodeString(value: string, { fields, path }: EncodeContext): string {
const stega = vercelStegaEncode({ decap: path });
const isMarkdown = fields[0]?.widget === 'markdown';
if (isMarkdown && value.includes('\n\n')) {
const blocks = value.split(/(\n\n+)/);
return blocks.map(block => (block.trim() ? block + stega : block)).join('');
}
return value + stega;
}
/**
* Encode a list of values, handling both simple values and nested objects/lists
* For typed lists, use the type field to determine which fields to use
*/
function encodeList(list: List<unknown>, ctx: EncodeContext): List<unknown> {
let newList = list;
for (let i = 0; i < newList.size; i++) {
const item = newList.get(i);
if (isImmutableMap(item)) {
const itemType = item.get('type');
if (typeof itemType === 'string') {
// For typed items, look up fields based on type
const field = ctx.fields.find(f => f.name === itemType);
const newItem = ctx.visit(item, getNestedFields(field), `${ctx.path}.${i}`);
newList = newList.set(i, newItem);
} else {
// For untyped items, use current fields
const newItem = ctx.visit(item, ctx.fields, `${ctx.path}.${i}`);
newList = newList.set(i, newItem);
}
} else {
// For simple values, use first field if available
const field = ctx.fields[0];
const newItem = ctx.visit(item, field ? [field] : [], `${ctx.path}.${i}`);
if (newItem !== item) {
newList = newList.set(i, newItem);
}
}
}
return newList;
}
/**
* Encode a map of values, looking up the appropriate field for each key
* and recursively encoding nested values
*/
function encodeMap(
map: ImmutableMap<string, unknown>,
ctx: EncodeContext,
): ImmutableMap<string, unknown> {
let newMap = map;
for (const [key, val] of newMap.entrySeq().toArray()) {
const field = ctx.fields.find(f => f.name === key);
if (field) {
const fields = getNestedFields(field);
const newVal = ctx.visit(val, fields, ctx.path ? `${ctx.path}.${key}` : key);
if (newVal !== val) {
newMap = newMap.set(key, newVal);
}
}
}
return newMap;
}
/**
* Main entry point for encoding steganographic data into entry values
* Uses a visitor pattern with caching to handle recursive structures
*/
export function encodeEntry(value: unknown, fields: List<ImmutableMap<string, unknown>>) {
const plainFields = fields.toJS() as CmsField[];
const cache = new Map();
function visit(value: unknown, fields: CmsField[], path = '') {
const cached = cache.get(path);
if (cached === value) return value;
const ctx: EncodeContext = { fields, path, visit };
let result;
if (isImmutableList(value)) {
result = encodeList(value, ctx);
} else if (isImmutableMap(value)) {
result = encodeMap(value, ctx);
} else if (typeof value === 'string') {
result = encodeString(value, ctx);
} else {
result = value;
}
cache.set(path, result);
return result;
}
return visit(value, plainFields);
}