@markdy/core 0.8.6 → 0.8.7
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/index.js +163 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -351,7 +351,7 @@ var EDGE_OPERATORS = {
|
|
|
351
351
|
};
|
|
352
352
|
var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
|
|
353
353
|
var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set(["show", "hide", "glow", "focus", "frame", "use"]);
|
|
354
|
-
var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction"]);
|
|
354
|
+
var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction", "layout"]);
|
|
355
355
|
function nodeRole(kind) {
|
|
356
356
|
return TECHNICAL_NODE_KINDS[kind] ?? "compute";
|
|
357
357
|
}
|
|
@@ -758,24 +758,79 @@ var ParseError = class extends Error {
|
|
|
758
758
|
}
|
|
759
759
|
};
|
|
760
760
|
var FLOW_OP_RE = /(->|<-|~>|--)/;
|
|
761
|
-
var PROP_RE = /(\w[\w.-]*)=(\S+)/g;
|
|
762
761
|
function stripComment(line) {
|
|
763
|
-
|
|
764
|
-
|
|
762
|
+
let inString = false;
|
|
763
|
+
let escaped = false;
|
|
764
|
+
for (let i = 0; i < line.length; i++) {
|
|
765
|
+
const ch = line[i];
|
|
766
|
+
if (escaped) {
|
|
767
|
+
escaped = false;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (inString && ch === "\\") {
|
|
771
|
+
escaped = true;
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (ch === '"') {
|
|
775
|
+
inString = !inString;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (inString) continue;
|
|
779
|
+
if (ch === "/" && line[i + 1] === "/") return line.slice(0, i);
|
|
780
|
+
if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i);
|
|
781
|
+
}
|
|
782
|
+
return line;
|
|
783
|
+
}
|
|
784
|
+
function parsePropValue(raw) {
|
|
785
|
+
let val = raw;
|
|
786
|
+
if (raw.startsWith('"')) {
|
|
787
|
+
const parsed = parseStringToken(raw);
|
|
788
|
+
if (parsed && parsed.rest === "") val = parsed.value;
|
|
789
|
+
}
|
|
790
|
+
if (typeof val === "string") {
|
|
791
|
+
if (/^\d+(\.\d+)?$/.test(val)) val = Number(val);
|
|
792
|
+
else if (val === "true") val = true;
|
|
793
|
+
else if (val === "false") val = false;
|
|
794
|
+
else if (/^\d+ms$/.test(val)) val = Number(val.slice(0, -2)) / 1e3;
|
|
795
|
+
else if (/^\d+(\.\d+)?s$/.test(val)) val = Number(val.slice(0, -1));
|
|
796
|
+
}
|
|
797
|
+
return val;
|
|
765
798
|
}
|
|
766
799
|
function parseProps(raw) {
|
|
767
800
|
const props = {};
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
else if (/^\d+(\.\d+)?s$/.test(val)) val = Number(val.slice(0, -1));
|
|
801
|
+
let i = 0;
|
|
802
|
+
while (i < raw.length) {
|
|
803
|
+
const ch = raw[i];
|
|
804
|
+
if (ch === '"') {
|
|
805
|
+
const parsed = parseStringToken(raw.slice(i));
|
|
806
|
+
if (!parsed) break;
|
|
807
|
+
i = raw.length - parsed.rest.length;
|
|
808
|
+
continue;
|
|
777
809
|
}
|
|
778
|
-
|
|
810
|
+
const keyMatch = raw.slice(i).match(/^(\w[\w.-]*)=/);
|
|
811
|
+
if (!keyMatch) {
|
|
812
|
+
i++;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const key = keyMatch[1];
|
|
816
|
+
i += key.length + 1;
|
|
817
|
+
let value = "";
|
|
818
|
+
if (raw[i] === '"') {
|
|
819
|
+
const start = i;
|
|
820
|
+
const parsed = parseStringToken(raw.slice(i));
|
|
821
|
+
if (!parsed) {
|
|
822
|
+
value = raw.slice(i);
|
|
823
|
+
i = raw.length;
|
|
824
|
+
} else {
|
|
825
|
+
i = raw.length - parsed.rest.length;
|
|
826
|
+
value = raw.slice(start, i).trim();
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
const start = i;
|
|
830
|
+
while (i < raw.length && !/\s/.test(raw[i])) i++;
|
|
831
|
+
value = raw.slice(start, i);
|
|
832
|
+
}
|
|
833
|
+
props[key] = parsePropValue(value);
|
|
779
834
|
}
|
|
780
835
|
return props;
|
|
781
836
|
}
|
|
@@ -800,6 +855,56 @@ function parseStringToken(raw) {
|
|
|
800
855
|
function splitTargets(raw) {
|
|
801
856
|
return raw.split(/[\s,]+/).map((t) => t.trim()).filter(Boolean);
|
|
802
857
|
}
|
|
858
|
+
function splitOutsideQuotes(raw, separator) {
|
|
859
|
+
const parts = [];
|
|
860
|
+
let start = 0;
|
|
861
|
+
let inString = false;
|
|
862
|
+
let escaped = false;
|
|
863
|
+
for (let i = 0; i < raw.length; i++) {
|
|
864
|
+
const ch = raw[i];
|
|
865
|
+
if (escaped) {
|
|
866
|
+
escaped = false;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (inString && ch === "\\") {
|
|
870
|
+
escaped = true;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (ch === '"') {
|
|
874
|
+
inString = !inString;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (!inString && ch === separator && /\s/.test(raw[i - 1] ?? "") && /\s/.test(raw[i + 1] ?? "")) {
|
|
878
|
+
parts.push(raw.slice(start, i).trim());
|
|
879
|
+
start = i + 1;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
parts.push(raw.slice(start).trim());
|
|
883
|
+
return parts.filter(Boolean);
|
|
884
|
+
}
|
|
885
|
+
function stripCueProps(raw, keys) {
|
|
886
|
+
let inString = false;
|
|
887
|
+
let escaped = false;
|
|
888
|
+
for (let i = 0; i < raw.length; i++) {
|
|
889
|
+
const ch = raw[i];
|
|
890
|
+
if (escaped) {
|
|
891
|
+
escaped = false;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (inString && ch === "\\") {
|
|
895
|
+
escaped = true;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (ch === '"') {
|
|
899
|
+
inString = !inString;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (inString || !/\s/.test(ch)) continue;
|
|
903
|
+
const rest = raw.slice(i + 1);
|
|
904
|
+
if (keys.some((key) => rest.startsWith(`${key}=`))) return raw.slice(0, i).trim();
|
|
905
|
+
}
|
|
906
|
+
return raw.trim();
|
|
907
|
+
}
|
|
803
908
|
function parseFlowChain(line, lineNo) {
|
|
804
909
|
const segments = [];
|
|
805
910
|
const parts = line.split(FLOW_OP_RE).map((p) => p.trim()).filter(Boolean);
|
|
@@ -837,16 +942,16 @@ function splitTargetLabel(token, lineNo) {
|
|
|
837
942
|
function parseCueLine(line, lineNo) {
|
|
838
943
|
const trimmed = line.trim();
|
|
839
944
|
const props = parseProps(trimmed);
|
|
840
|
-
|
|
841
|
-
|
|
945
|
+
const parallelParts = splitOutsideQuotes(trimmed, "&");
|
|
946
|
+
if (parallelParts.length > 1) {
|
|
842
947
|
return {
|
|
843
948
|
kind: "parallel",
|
|
844
|
-
cues:
|
|
949
|
+
cues: parallelParts.map((p, idx) => parseCueLine(p, lineNo + idx * 1e-3)),
|
|
845
950
|
line: lineNo
|
|
846
951
|
};
|
|
847
952
|
}
|
|
848
953
|
if (FLOW_OP_RE.test(trimmed)) {
|
|
849
|
-
const chainPart = trimmed
|
|
954
|
+
const chainPart = stripCueProps(trimmed, ["dur", "stagger", "color", "strength", "zoom", "after"]);
|
|
850
955
|
return {
|
|
851
956
|
kind: "flow",
|
|
852
957
|
segments: parseFlowChain(chainPart, lineNo),
|
|
@@ -857,7 +962,7 @@ function parseCueLine(line, lineNo) {
|
|
|
857
962
|
const [head, ...rest] = trimmed.split(/\s+/);
|
|
858
963
|
const keyword = head.toLowerCase();
|
|
859
964
|
if (keyword === "show" || keyword === "hide") {
|
|
860
|
-
const targetRaw = rest.join(" ")
|
|
965
|
+
const targetRaw = stripCueProps(rest.join(" "), ["dur", "stagger"]);
|
|
861
966
|
return {
|
|
862
967
|
kind: keyword,
|
|
863
968
|
targets: splitTargets(targetRaw),
|
|
@@ -867,7 +972,7 @@ function parseCueLine(line, lineNo) {
|
|
|
867
972
|
};
|
|
868
973
|
}
|
|
869
974
|
if (keyword === "glow") {
|
|
870
|
-
const targetRaw = rest.join(" ")
|
|
975
|
+
const targetRaw = stripCueProps(rest.join(" "), ["color", "strength", "dur"]);
|
|
871
976
|
return {
|
|
872
977
|
kind: "glow",
|
|
873
978
|
targets: splitTargets(targetRaw),
|
|
@@ -878,7 +983,7 @@ function parseCueLine(line, lineNo) {
|
|
|
878
983
|
};
|
|
879
984
|
}
|
|
880
985
|
if (keyword === "focus") {
|
|
881
|
-
const targetRaw = rest.join(" ")
|
|
986
|
+
const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
|
|
882
987
|
return {
|
|
883
988
|
kind: "focus",
|
|
884
989
|
targets: splitTargets(targetRaw),
|
|
@@ -888,7 +993,7 @@ function parseCueLine(line, lineNo) {
|
|
|
888
993
|
};
|
|
889
994
|
}
|
|
890
995
|
if (keyword === "frame") {
|
|
891
|
-
const targetRaw = rest.join(" ")
|
|
996
|
+
const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
|
|
892
997
|
const targets = splitTargets(targetRaw);
|
|
893
998
|
if (targets.length === 0) throw new ParseError(`expected frame target`, lineNo);
|
|
894
999
|
return {
|
|
@@ -992,6 +1097,30 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
|
|
|
992
1097
|
}
|
|
993
1098
|
return { body, nextIdx: i };
|
|
994
1099
|
}
|
|
1100
|
+
function readBody(blocks, startIdx, parentIndent, braceDelimited) {
|
|
1101
|
+
if (!braceDelimited) return readIndentedBody(blocks, startIdx, parentIndent);
|
|
1102
|
+
const body = [];
|
|
1103
|
+
let i = startIdx;
|
|
1104
|
+
while (i < blocks.length) {
|
|
1105
|
+
if (blocks[i].text === "}") return { body, nextIdx: i + 1 };
|
|
1106
|
+
body.push(blocks[i]);
|
|
1107
|
+
i++;
|
|
1108
|
+
}
|
|
1109
|
+
return { body, nextIdx: i };
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeCueBlocks(blocks) {
|
|
1112
|
+
const normalized = [];
|
|
1113
|
+
for (const block of blocks) {
|
|
1114
|
+
if (block.text.startsWith("& ")) {
|
|
1115
|
+
const prev = normalized[normalized.length - 1];
|
|
1116
|
+
if (!prev) throw new ParseError(`parallel continuation must follow a cue`, block.line);
|
|
1117
|
+
prev.text = `${prev.text} ${block.text}`;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
normalized.push({ ...block });
|
|
1121
|
+
}
|
|
1122
|
+
return normalized;
|
|
1123
|
+
}
|
|
995
1124
|
function pushWarning(diagnostics, seen, line, message) {
|
|
996
1125
|
const key = `${line}:${message}`;
|
|
997
1126
|
if (seen.has(key)) return;
|
|
@@ -1085,6 +1214,11 @@ function parse(source, opts = {}) {
|
|
|
1085
1214
|
title = str.value;
|
|
1086
1215
|
remainder = str.rest;
|
|
1087
1216
|
}
|
|
1217
|
+
const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
|
|
1218
|
+
if (inlineLayout) {
|
|
1219
|
+
meta.direction = inlineLayout[1].toUpperCase();
|
|
1220
|
+
remainder = remainder.replace(/\blayout\s+(LR|RL|TB|BT)\b/i, " ");
|
|
1221
|
+
}
|
|
1088
1222
|
const props = parseProps(remainder);
|
|
1089
1223
|
for (const [k, v] of Object.entries(props)) {
|
|
1090
1224
|
if (!SCENE_KEYS.has(k)) {
|
|
@@ -1094,7 +1228,7 @@ function parse(source, opts = {}) {
|
|
|
1094
1228
|
if (k === "width" || k === "height" || k === "fps") meta[k] = Number(v);
|
|
1095
1229
|
else if (k === "duration") meta.duration = Number(v);
|
|
1096
1230
|
else if (k === "theme") meta.theme = String(v);
|
|
1097
|
-
else if (k === "direction") meta.direction = String(v).toUpperCase();
|
|
1231
|
+
else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
|
|
1098
1232
|
}
|
|
1099
1233
|
i++;
|
|
1100
1234
|
continue;
|
|
@@ -1112,11 +1246,11 @@ function parse(source, opts = {}) {
|
|
|
1112
1246
|
continue;
|
|
1113
1247
|
}
|
|
1114
1248
|
if (line.startsWith("pattern ")) {
|
|
1115
|
-
const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s
|
|
1249
|
+
const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s*(?::|\{)\s*$/);
|
|
1116
1250
|
if (!m) throw new ParseError(`expected pattern name(params):`, lineNo);
|
|
1117
1251
|
const params = m[2].trim() ? m[2].split(",").map((p) => p.trim()) : [];
|
|
1118
|
-
const { body, nextIdx } =
|
|
1119
|
-
const cues = body.map((b) => parseCueLine(b.text, b.line));
|
|
1252
|
+
const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
|
|
1253
|
+
const cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
|
|
1120
1254
|
patterns[m[1]] = { name: m[1], params, body: cues, line: lineNo };
|
|
1121
1255
|
i = nextIdx;
|
|
1122
1256
|
continue;
|
|
@@ -1153,10 +1287,10 @@ function parse(source, opts = {}) {
|
|
|
1153
1287
|
continue;
|
|
1154
1288
|
}
|
|
1155
1289
|
if (line.startsWith("beat ")) {
|
|
1156
|
-
const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s
|
|
1290
|
+
const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
|
|
1157
1291
|
if (!m) throw new ParseError(`expected beat name:`, lineNo);
|
|
1158
|
-
const { body, nextIdx } =
|
|
1159
|
-
let cues = body.map((b) => parseCueLine(b.text, b.line));
|
|
1292
|
+
const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
|
|
1293
|
+
let cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
|
|
1160
1294
|
cues = expandPatternCues(cues, patterns, lineNo);
|
|
1161
1295
|
beats.push({ name: m[1], label: m[2], cues, line: lineNo });
|
|
1162
1296
|
i = nextIdx;
|
package/package.json
CHANGED