feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
这个提交包含在:
@@ -0,0 +1,122 @@
|
||||
// scrollmap is used for synchronous scrolling between Markdown Editor and Viewer.
|
||||
// It has the mapping information between the line numbers of a Markdown text and
|
||||
// the scroll positions (percents) of the elements in the HTML document transformed
|
||||
// from the Markdown text.
|
||||
// To see the detail of synchronous scrolling, refer the following design document.
|
||||
// <s> Replace me! https://github.com/laurent22/joplin/pull/5512#issuecomment-931277022 </s>
|
||||
|
||||
const scrollmap = {
|
||||
map_: null,
|
||||
lineCount_: 0,
|
||||
};
|
||||
|
||||
scrollmap.create = (lineCount) => {
|
||||
// Creates a translation map between editor's line number
|
||||
// and viewer's scroll percent. Both attributes (line and percent) of
|
||||
// the returned map are sorted respectively.
|
||||
// For each document change, this function should be called.
|
||||
// Since creating this map is costly for each scroll event,
|
||||
// it is cached and re-created as needed. Whenever the layout
|
||||
// of the document changes, it has to be invalidated by refresh().
|
||||
scrollmap.lineCount_ = lineCount;
|
||||
scrollmap.refresh();
|
||||
};
|
||||
|
||||
scrollmap.refresh = () => {
|
||||
scrollmap.map_ = null;
|
||||
};
|
||||
|
||||
scrollmap.get_ = () => {
|
||||
if (scrollmap.map_) return scrollmap.map_;
|
||||
const contentElement = document.getElementById('joplin-container-content');
|
||||
if (!contentElement) return null;
|
||||
const height = Math.max(1, contentElement.scrollHeight - contentElement.clientHeight);
|
||||
// Since getBoundingClientRect() returns a relative position,
|
||||
// the offset of the origin is needed to get its aboslute position.
|
||||
const firstElem = document.getElementById('rendered-md');
|
||||
if (!firstElem) return null;
|
||||
const offset = firstElem.getBoundingClientRect().top;
|
||||
// Mapping information between editor's lines and viewer's elements is
|
||||
// embedded into elements by the renderer.
|
||||
// See also renderer/MdToHtml/rules/source_map.ts.
|
||||
const elems = document.getElementsByClassName('maps-to-line');
|
||||
if (elems.length === 0) return null;
|
||||
const map = { line: [0], percent: [0], viewHeight: height, lineCount: 0 };
|
||||
// Each map entry is total-ordered.
|
||||
let last = 0;
|
||||
for (let i = 0; i < elems.length; i++) {
|
||||
const rect = elems[i].getBoundingClientRect();
|
||||
const top = rect.top - offset;
|
||||
const line = Number(elems[i].getAttribute('source-line'));
|
||||
const percent = Math.max(0, Math.min(1, top / height));
|
||||
if (map.line[last] < line && map.percent[last] < percent) {
|
||||
map.line.push(line);
|
||||
map.percent.push(percent);
|
||||
last += 1;
|
||||
}
|
||||
const bottom = rect.bottom - offset;
|
||||
const lineEnd = Number(elems[i].getAttribute('source-line-end'));
|
||||
const percentEnd = Math.max(0, Math.min(1, bottom / height));
|
||||
if (map.line[last] < lineEnd && map.percent[last] < percentEnd) {
|
||||
map.line.push(lineEnd);
|
||||
map.percent.push(percentEnd);
|
||||
last += 1;
|
||||
}
|
||||
}
|
||||
const lineCount = scrollmap.lineCount_;
|
||||
if (lineCount) {
|
||||
map.lineCount = lineCount;
|
||||
} else {
|
||||
if (map.lineCount < map.line[last]) map.lineCount = map.line[last];
|
||||
}
|
||||
if (map.percent[last] < 1) {
|
||||
map.line.push(lineCount || 1e10);
|
||||
map.percent.push(1);
|
||||
} else {
|
||||
map.line[last] = lineCount || 1e10;
|
||||
}
|
||||
scrollmap.map_ = map;
|
||||
return map;
|
||||
};
|
||||
|
||||
scrollmap.isPresent = () => {
|
||||
const map = scrollmap.get_();
|
||||
return !!map;
|
||||
};
|
||||
|
||||
scrollmap.translateLV_ = (percent, l2v = true) => {
|
||||
// If the input is out of (0,1) or not number, it is not translated.
|
||||
if (!(0 < percent && percent < 1)) return percent;
|
||||
const map = scrollmap.get_();
|
||||
if (!map || map.line.length <= 2) return percent; // No translation
|
||||
const lineCount = map.lineCount;
|
||||
const values = l2v ? map.line : map.percent;
|
||||
const target = l2v ? percent * lineCount : percent;
|
||||
// Binary search (rightmost): finds where map[r-1][field] <= target < map[r][field]
|
||||
let l = 1, r = values.length - 1;
|
||||
while (l < r) {
|
||||
const m = Math.floor(l + (r - l) / 2);
|
||||
if (target < values[m]) r = m; else l = m + 1;
|
||||
}
|
||||
const lineU = map.line[r - 1];
|
||||
const lineL = Math.min(lineCount, map.line[r]);
|
||||
const vPercentU = map.percent[r - 1];
|
||||
const vPercentL = map.percent[r];
|
||||
let linInterp, result;
|
||||
if (l2v) {
|
||||
linInterp = (percent * lineCount - lineU) / (lineL - lineU);
|
||||
result = vPercentU + (vPercentL - vPercentU) * linInterp;
|
||||
} else {
|
||||
linInterp = (percent - vPercentU) / (vPercentL - vPercentU);
|
||||
result = (lineU + (lineL - lineU) * linInterp) / lineCount;
|
||||
}
|
||||
return Math.max(0, Math.min(1, result));
|
||||
};
|
||||
|
||||
scrollmap.translateL2V = (lPercent) => {
|
||||
return scrollmap.translateLV_(lPercent, true);
|
||||
};
|
||||
|
||||
scrollmap.translateV2L = (vPercent) => {
|
||||
return scrollmap.translateLV_(vPercent, false);
|
||||
};
|
||||
在新工单中引用
屏蔽一个用户