@hyperframes/lint 0.8.106 → 0.8.108
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/browser.js +40 -22
- package/dist/browser.js.map +1 -1
- package/dist/index.js +48 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -113,6 +113,28 @@ function readAttr(tagSource, attr) {
|
|
|
113
113
|
const match = tagSource.match(new RegExp(`(?<![\\w-])${escaped}\\s*=\\s*["']([^"']+)["']`, "i"));
|
|
114
114
|
return match?.[1] || null;
|
|
115
115
|
}
|
|
116
|
+
function hasAttrName(tagSource, attr) {
|
|
117
|
+
const escaped = attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
118
|
+
const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
|
|
119
|
+
return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
|
|
120
|
+
}
|
|
121
|
+
function isAudibleVideoTag(tagSource) {
|
|
122
|
+
if (hasAttrName(tagSource, "muted")) return false;
|
|
123
|
+
if (!hasAttrName(tagSource, "data-has-audio")) return true;
|
|
124
|
+
const declared = readAttr(tagSource, "data-has-audio");
|
|
125
|
+
return declared === "true";
|
|
126
|
+
}
|
|
127
|
+
function mediaTimeWindow(tagSource) {
|
|
128
|
+
const start = Number(readAttr(tagSource, "data-start"));
|
|
129
|
+
const duration = Number(readAttr(tagSource, "data-duration"));
|
|
130
|
+
if (!readAttr(tagSource, "data-start") || !readAttr(tagSource, "data-duration")) return null;
|
|
131
|
+
if (!Number.isFinite(start) || !Number.isFinite(duration)) return null;
|
|
132
|
+
return { start, end: start + duration };
|
|
133
|
+
}
|
|
134
|
+
function mediaWindowsOverlap(a, b) {
|
|
135
|
+
if (!a || !b) return true;
|
|
136
|
+
return a.start < b.end && b.start < a.end;
|
|
137
|
+
}
|
|
116
138
|
function readDecodedAttr(tagSource, attr) {
|
|
117
139
|
if (!tagSource) return null;
|
|
118
140
|
let value = null;
|
|
@@ -1367,11 +1389,6 @@ function tweensVolumeInSameCall(script, id) {
|
|
|
1367
1389
|
function escapeRegExp(value) {
|
|
1368
1390
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1369
1391
|
}
|
|
1370
|
-
function hasAttrName(tagSource, attr) {
|
|
1371
|
-
const escaped = escapeRegExp(attr);
|
|
1372
|
-
const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
|
|
1373
|
-
return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
|
|
1374
|
-
}
|
|
1375
1392
|
var IMAGE_SRC_EXT = /* @__PURE__ */ new Set([
|
|
1376
1393
|
"jpg",
|
|
1377
1394
|
"jpeg",
|
|
@@ -1772,14 +1789,15 @@ var mediaRules = [
|
|
|
1772
1789
|
if (tag.name !== "video") continue;
|
|
1773
1790
|
const hasMuted = hasAttrName(tag.raw, "muted");
|
|
1774
1791
|
const hasDeclaredAudio = readAttr(tag.raw, "data-has-audio") === "true";
|
|
1775
|
-
|
|
1792
|
+
const declaresAudioState = hasAttrName(tag.raw, "data-has-audio");
|
|
1793
|
+
if (!hasMuted && !declaresAudioState && readAttr(tag.raw, "data-start")) {
|
|
1776
1794
|
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
1777
1795
|
findings.push({
|
|
1778
1796
|
code: "video_missing_muted",
|
|
1779
1797
|
severity: "error",
|
|
1780
|
-
message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but
|
|
1798
|
+
message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but declares neither muted nor data-has-audio. If the file has sound, add data-has-audio="true" (the sound stays on this clip). If it is silent, add muted.`,
|
|
1781
1799
|
elementId,
|
|
1782
|
-
fixHint: 'Add
|
|
1800
|
+
fixHint: 'Add data-has-audio="true" when the file has sound (recommended \u2014 keeps picture and sound on one clip), or add `muted` for silent footage.',
|
|
1783
1801
|
snippet: truncateSnippet(tag.raw)
|
|
1784
1802
|
});
|
|
1785
1803
|
}
|
|
@@ -2005,31 +2023,31 @@ var mediaRules = [
|
|
|
2005
2023
|
// <audio> pointing to the same file, which causes double playback at runtime
|
|
2006
2024
|
({ tags }) => {
|
|
2007
2025
|
const findings = [];
|
|
2008
|
-
const
|
|
2009
|
-
const
|
|
2026
|
+
const videos = [];
|
|
2027
|
+
const audios = [];
|
|
2010
2028
|
for (const tag of tags) {
|
|
2011
2029
|
if (!readAttr(tag.raw, "data-start")) continue;
|
|
2012
2030
|
const src = readAttr(tag.raw, "src");
|
|
2013
2031
|
if (!src) continue;
|
|
2014
|
-
const
|
|
2032
|
+
const source = { id: readAttr(tag.raw, "id") || void 0, raw: tag.raw };
|
|
2015
2033
|
if (tag.name === "video") {
|
|
2016
|
-
|
|
2017
|
-
if (!isMuted) {
|
|
2018
|
-
videoSources.set(src, { id: elementId, raw: tag.raw });
|
|
2019
|
-
}
|
|
2034
|
+
if (isAudibleVideoTag(tag.raw)) videos.push([src, source]);
|
|
2020
2035
|
} else if (tag.name === "audio") {
|
|
2021
|
-
|
|
2036
|
+
audios.push([src, source]);
|
|
2022
2037
|
}
|
|
2023
2038
|
}
|
|
2024
|
-
for (const [src, audioInfo] of
|
|
2025
|
-
const
|
|
2026
|
-
|
|
2039
|
+
for (const [src, audioInfo] of audios) {
|
|
2040
|
+
const match = videos.find(
|
|
2041
|
+
([videoSrc, video]) => videoSrc === src && mediaWindowsOverlap(mediaTimeWindow(video.raw), mediaTimeWindow(audioInfo.raw))
|
|
2042
|
+
);
|
|
2043
|
+
if (!match) continue;
|
|
2044
|
+
const videoInfo = match[1];
|
|
2027
2045
|
findings.push({
|
|
2028
2046
|
code: "video_audio_double_source",
|
|
2029
2047
|
severity: "error",
|
|
2030
|
-
message: `<audio${audioInfo.id ? ` id="${audioInfo.id}"` : ""}> and <video${videoInfo.id ? ` id="${videoInfo.id}"` : ""}> both point to the same source. The unmuted video already provides audio \u2014 the duplicate <audio> will cause double playback and echo.`,
|
|
2048
|
+
message: `<audio${audioInfo.id ? ` id="${audioInfo.id}"` : ""}> and <video${videoInfo.id ? ` id="${videoInfo.id}"` : ""}> both point to the same source at the same time. The unmuted video already provides audio \u2014 the duplicate <audio> will cause double playback and echo.`,
|
|
2031
2049
|
elementId: audioInfo.id,
|
|
2032
|
-
fixHint: "
|
|
2050
|
+
fixHint: "Remove the <audio> element and let the video carry its own sound (recommended), or mute the video (add `muted`) and keep the separate <audio> when picture and sound must be cut independently.",
|
|
2033
2051
|
snippet: truncateSnippet(audioInfo.raw)
|
|
2034
2052
|
});
|
|
2035
2053
|
}
|
|
@@ -2138,7 +2156,7 @@ function findCarveUngroupedSourcesFindings(ctx) {
|
|
|
2138
2156
|
var AUDIO_GROUP_TIMING_ATTRS = ["data-start", "data-duration", "data-track-index"];
|
|
2139
2157
|
function findAudioGroupNoMembersFindings(ctx) {
|
|
2140
2158
|
const memberGroupIds = new Set(
|
|
2141
|
-
ctx.tags.filter((tag) => tag.name === "audio").map((tag) => readAttr(tag.raw, "data-audio-group")).filter((id) => Boolean(id))
|
|
2159
|
+
ctx.tags.filter((tag) => tag.name === "audio" || tag.name === "video" && isAudibleVideoTag(tag.raw)).map((tag) => readAttr(tag.raw, "data-audio-group")).filter((id) => Boolean(id))
|
|
2142
2160
|
);
|
|
2143
2161
|
if (memberGroupIds.size === 0) return [];
|
|
2144
2162
|
const mayHaveCrossFileMembers = ctx.tags.some(
|