From 0e9afde5fa87d7039d4cebd43e5613541c56849a Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Tue, 11 Apr 2017 21:33:54 +0800 Subject: Move syncsroll under lib --- public/js/index.js | 2 +- public/js/lib/syncscroll.js | 368 ++++++++++++++++++++++++++++++++++++++++++++ public/js/syncscroll.js | 368 -------------------------------------------- 3 files changed, 369 insertions(+), 369 deletions(-) create mode 100644 public/js/lib/syncscroll.js delete mode 100644 public/js/syncscroll.js (limited to 'public/js') diff --git a/public/js/index.js b/public/js/index.js index 0ca6827b..245cf299 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -65,7 +65,7 @@ import { setupSyncAreas, syncScrollToEdit, syncScrollToView -} from './syncscroll' +} from './lib/syncscroll' import { writeHistory, diff --git a/public/js/lib/syncscroll.js b/public/js/lib/syncscroll.js new file mode 100644 index 00000000..6b07e79c --- /dev/null +++ b/public/js/lib/syncscroll.js @@ -0,0 +1,368 @@ +/* eslint-env browser, jquery */ +/* global _ */ +// Inject line numbers for sync scroll. + +import markdownitContainer from 'markdown-it-container' + +import { md } from '../extra' +import modeType from '../lib/editor/modeType' + +function addPart (tokens, idx) { + if (tokens[idx].map && tokens[idx].level === 0) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + tokens[idx].attrJoin('class', 'part') + tokens[idx].attrJoin('data-startline', startline) + tokens[idx].attrJoin('data-endline', endline) + } +} + +md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) { + tokens[idx].attrJoin('class', 'raw') + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.table_open = function (tokens, idx, options, env, self) { + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) { + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { + tokens[idx].attrJoin('class', 'raw') + if (tokens[idx].map) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + tokens[idx].attrJoin('data-startline', startline) + tokens[idx].attrJoin('data-endline', endline) + } + return self.renderToken(...arguments) +} +md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) { + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.link_open = function (tokens, idx, options, env, self) { + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { + tokens[idx].attrJoin('class', 'raw') + addPart(tokens, idx) + return self.renderToken(...arguments) +} +md.renderer.rules.fence = (tokens, idx, options, env, self) => { + const token = tokens[idx] + const info = token.info ? md.utils.unescapeAll(token.info).trim() : '' + let langName = '' + let highlighted + + if (info) { + langName = info.split(/\s+/g)[0] + if (/!$/.test(info)) token.attrJoin('class', 'wrap') + token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!/, '')) + token.attrJoin('class', 'hljs') + token.attrJoin('class', 'raw') + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content) + } else { + highlighted = md.utils.escapeHtml(token.content) + } + + if (highlighted.indexOf('${highlighted}\n` + } + + return `
${highlighted}
\n` +} +md.renderer.rules.code_block = (tokens, idx, options, env, self) => { + if (tokens[idx].map && tokens[idx].level === 0) { + const startline = tokens[idx].map[0] + 1 + const endline = tokens[idx].map[1] + return `
${md.utils.escapeHtml(tokens[idx].content)}
\n` + } + return `
${md.utils.escapeHtml(tokens[idx].content)}
\n` +} +function renderContainer (tokens, idx, options, env, self) { + tokens[idx].attrJoin('role', 'alert') + tokens[idx].attrJoin('class', 'alert') + tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`) + addPart(tokens, idx) + return self.renderToken(...arguments) +} + +md.use(markdownitContainer, 'success', { render: renderContainer }) +md.use(markdownitContainer, 'info', { render: renderContainer }) +md.use(markdownitContainer, 'warning', { render: renderContainer }) +md.use(markdownitContainer, 'danger', { render: renderContainer }) + +// FIXME: expose syncscroll to window +window.syncscroll = true + +window.preventSyncScrollToEdit = false +window.preventSyncScrollToView = false + +const editScrollThrottle = 5 +const viewScrollThrottle = 5 +const buildMapThrottle = 100 + +let viewScrolling = false +let editScrolling = false + +let editArea = null +let viewArea = null +let markdownArea = null + +export function setupSyncAreas (edit, view, markdown) { + editArea = edit + viewArea = view + markdownArea = markdown + editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle)) + viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)) +} + +let scrollMap, lineHeightMap, viewTop, viewBottom + +export function clearMap () { + scrollMap = null + lineHeightMap = null + viewTop = null + viewBottom = null +} +window.viewAjaxCallback = clearMap + +const buildMap = _.throttle(buildMapInner, buildMapThrottle) + +// Build offsets for each line (lines can be wrapped) +// That's a bit dirty to process each line everytime, but ok for demo. +// Optimizations are required only for big texts. +function buildMapInner (callback) { + if (!viewArea || !markdownArea) return + let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap + + offset = viewArea.scrollTop() - viewArea.offset().top + _scrollMap = [] + nonEmptyList = [] + _lineHeightMap = [] + viewTop = 0 + viewBottom = viewArea[0].scrollHeight - viewArea.height() + + acc = 0 + const lines = window.editor.getValue().split('\n') + const lineHeight = window.editor.defaultTextHeight() + for (i = 0; i < lines.length; i++) { + const str = lines[i] + + _lineHeightMap.push(acc) + + if (str.length === 0) { + acc++ + continue + } + + const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i) + acc += Math.round(h / lineHeight) + } + _lineHeightMap.push(acc) + linesCount = acc + + for (i = 0; i < linesCount; i++) { + _scrollMap.push(-1) + } + + nonEmptyList.push(0) + // make the first line go top + _scrollMap[0] = viewTop + + const parts = markdownArea.find('.part').toArray() + for (i = 0; i < parts.length; i++) { + const $el = $(parts[i]) + let t = $el.attr('data-startline') - 1 + if (t === '') { + return + } + t = _lineHeightMap[t] + if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) { + nonEmptyList.push(t) + } + _scrollMap[t] = Math.round($el.offset().top + offset - 10) + } + + nonEmptyList.push(linesCount) + _scrollMap[linesCount] = viewArea[0].scrollHeight + + pos = 0 + for (i = 1; i < linesCount; i++) { + if (_scrollMap[i] !== -1) { + pos++ + continue + } + + a = nonEmptyList[pos] + b = nonEmptyList[pos + 1] + _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a)) + } + + _scrollMap[0] = 0 + + scrollMap = _scrollMap + lineHeightMap = _lineHeightMap + + if (window.loaded && callback) callback() +} + +// sync view scroll progress to edit +let viewScrollingTimer = null + +export function syncScrollToEdit (event, preventAnimate) { + if (window.currentMode !== modeType.both || !window.syncscroll || !editArea) return + if (window.preventSyncScrollToEdit) { + if (typeof window.preventSyncScrollToEdit === 'number') { + window.preventSyncScrollToEdit-- + } else { + window.preventSyncScrollToEdit = false + } + return + } + if (!scrollMap || !lineHeightMap) { + buildMap(() => { + syncScrollToEdit(event, preventAnimate) + }) + return + } + if (editScrolling) return + + const scrollTop = viewArea[0].scrollTop + let lineIndex = 0 + for (let i = 0, l = scrollMap.length; i < l; i++) { + if (scrollMap[i] > scrollTop) { + break + } else { + lineIndex = i + } + } + let lineNo = 0 + let lineDiff = 0 + for (let i = 0, l = lineHeightMap.length; i < l; i++) { + if (lineHeightMap[i] > lineIndex) { + break + } else { + lineNo = lineHeightMap[i] + lineDiff = lineHeightMap[i + 1] - lineNo + } + } + + let posTo = 0 + let topDiffPercent = 0 + let posToNextDiff = 0 + const scrollInfo = window.editor.getScrollInfo() + const textHeight = window.editor.defaultTextHeight() + const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight + const preLastLineNo = Math.round(preLastLineHeight / textHeight) + const preLastLinePos = scrollMap[preLastLineNo] + + if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) { + posTo = preLastLineHeight + topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos) + posToNextDiff = textHeight * topDiffPercent + posTo += Math.ceil(posToNextDiff) + } else { + posTo = lineNo * textHeight + topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]) + posToNextDiff = textHeight * lineDiff * topDiffPercent + posTo += Math.ceil(posToNextDiff) + } + + if (preventAnimate) { + editArea.scrollTop(posTo) + } else { + const posDiff = Math.abs(scrollInfo.top - posTo) + var duration = posDiff / 50 + duration = duration >= 100 ? duration : 100 + editArea.stop(true, true).animate({ + scrollTop: posTo + }, duration, 'linear') + } + + viewScrolling = true + clearTimeout(viewScrollingTimer) + viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5) +} + +function viewScrollingTimeoutInner () { + viewScrolling = false +} + +// sync edit scroll progress to view +let editScrollingTimer = null + +export function syncScrollToView (event, preventAnimate) { + if (window.currentMode !== modeType.both || !window.syncscroll || !viewArea) return + if (window.preventSyncScrollToView) { + if (typeof preventSyncScrollToView === 'number') { + window.preventSyncScrollToView-- + } else { + window.preventSyncScrollToView = false + } + return + } + if (!scrollMap || !lineHeightMap) { + buildMap(() => { + syncScrollToView(event, preventAnimate) + }) + return + } + if (viewScrolling) return + + let lineNo, posTo + let topDiffPercent, posToNextDiff + const scrollInfo = window.editor.getScrollInfo() + const textHeight = window.editor.defaultTextHeight() + lineNo = Math.floor(scrollInfo.top / textHeight) + // if reach the last line, will start lerp to the bottom + const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight) + if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) { + topDiffPercent = diffToBottom / textHeight + posTo = scrollMap[lineNo + 1] + posToNextDiff = (viewBottom - posTo) * topDiffPercent + posTo += Math.floor(posToNextDiff) + } else { + topDiffPercent = (scrollInfo.top % textHeight) / textHeight + posTo = scrollMap[lineNo] + posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent + posTo += Math.floor(posToNextDiff) + } + + if (preventAnimate) { + viewArea.scrollTop(posTo) + } else { + const posDiff = Math.abs(viewArea.scrollTop() - posTo) + var duration = posDiff / 50 + duration = duration >= 100 ? duration : 100 + viewArea.stop(true, true).animate({ + scrollTop: posTo + }, duration, 'linear') + } + + editScrolling = true + clearTimeout(editScrollingTimer) + editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5) +} + +function editScrollingTimeoutInner () { + editScrolling = false +} diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js deleted file mode 100644 index ad21e57b..00000000 --- a/public/js/syncscroll.js +++ /dev/null @@ -1,368 +0,0 @@ -/* eslint-env browser, jquery */ -/* global _ */ -// Inject line numbers for sync scroll. - -import markdownitContainer from 'markdown-it-container' - -import { md } from './extra' -import modeType from './lib/editor/modeType' - -function addPart (tokens, idx) { - if (tokens[idx].map && tokens[idx].level === 0) { - const startline = tokens[idx].map[0] + 1 - const endline = tokens[idx].map[1] - tokens[idx].attrJoin('class', 'part') - tokens[idx].attrJoin('data-startline', startline) - tokens[idx].attrJoin('data-endline', endline) - } -} - -md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw') - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.table_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw') - if (tokens[idx].map) { - const startline = tokens[idx].map[0] + 1 - const endline = tokens[idx].map[1] - tokens[idx].attrJoin('data-startline', startline) - tokens[idx].attrJoin('data-endline', endline) - } - return self.renderToken(...arguments) -} -md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.link_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { - tokens[idx].attrJoin('class', 'raw') - addPart(tokens, idx) - return self.renderToken(...arguments) -} -md.renderer.rules.fence = (tokens, idx, options, env, self) => { - const token = tokens[idx] - const info = token.info ? md.utils.unescapeAll(token.info).trim() : '' - let langName = '' - let highlighted - - if (info) { - langName = info.split(/\s+/g)[0] - if (/!$/.test(info)) token.attrJoin('class', 'wrap') - token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!/, '')) - token.attrJoin('class', 'hljs') - token.attrJoin('class', 'raw') - } - - if (options.highlight) { - highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content) - } else { - highlighted = md.utils.escapeHtml(token.content) - } - - if (highlighted.indexOf('${highlighted}\n` - } - - return `
${highlighted}
\n` -} -md.renderer.rules.code_block = (tokens, idx, options, env, self) => { - if (tokens[idx].map && tokens[idx].level === 0) { - const startline = tokens[idx].map[0] + 1 - const endline = tokens[idx].map[1] - return `
${md.utils.escapeHtml(tokens[idx].content)}
\n` - } - return `
${md.utils.escapeHtml(tokens[idx].content)}
\n` -} -function renderContainer (tokens, idx, options, env, self) { - tokens[idx].attrJoin('role', 'alert') - tokens[idx].attrJoin('class', 'alert') - tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`) - addPart(tokens, idx) - return self.renderToken(...arguments) -} - -md.use(markdownitContainer, 'success', { render: renderContainer }) -md.use(markdownitContainer, 'info', { render: renderContainer }) -md.use(markdownitContainer, 'warning', { render: renderContainer }) -md.use(markdownitContainer, 'danger', { render: renderContainer }) - -// FIXME: expose syncscroll to window -window.syncscroll = true - -window.preventSyncScrollToEdit = false -window.preventSyncScrollToView = false - -const editScrollThrottle = 5 -const viewScrollThrottle = 5 -const buildMapThrottle = 100 - -let viewScrolling = false -let editScrolling = false - -let editArea = null -let viewArea = null -let markdownArea = null - -export function setupSyncAreas (edit, view, markdown) { - editArea = edit - viewArea = view - markdownArea = markdown - editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle)) - viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)) -} - -let scrollMap, lineHeightMap, viewTop, viewBottom - -export function clearMap () { - scrollMap = null - lineHeightMap = null - viewTop = null - viewBottom = null -} -window.viewAjaxCallback = clearMap - -const buildMap = _.throttle(buildMapInner, buildMapThrottle) - -// Build offsets for each line (lines can be wrapped) -// That's a bit dirty to process each line everytime, but ok for demo. -// Optimizations are required only for big texts. -function buildMapInner (callback) { - if (!viewArea || !markdownArea) return - let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap - - offset = viewArea.scrollTop() - viewArea.offset().top - _scrollMap = [] - nonEmptyList = [] - _lineHeightMap = [] - viewTop = 0 - viewBottom = viewArea[0].scrollHeight - viewArea.height() - - acc = 0 - const lines = window.editor.getValue().split('\n') - const lineHeight = window.editor.defaultTextHeight() - for (i = 0; i < lines.length; i++) { - const str = lines[i] - - _lineHeightMap.push(acc) - - if (str.length === 0) { - acc++ - continue - } - - const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i) - acc += Math.round(h / lineHeight) - } - _lineHeightMap.push(acc) - linesCount = acc - - for (i = 0; i < linesCount; i++) { - _scrollMap.push(-1) - } - - nonEmptyList.push(0) - // make the first line go top - _scrollMap[0] = viewTop - - const parts = markdownArea.find('.part').toArray() - for (i = 0; i < parts.length; i++) { - const $el = $(parts[i]) - let t = $el.attr('data-startline') - 1 - if (t === '') { - return - } - t = _lineHeightMap[t] - if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) { - nonEmptyList.push(t) - } - _scrollMap[t] = Math.round($el.offset().top + offset - 10) - } - - nonEmptyList.push(linesCount) - _scrollMap[linesCount] = viewArea[0].scrollHeight - - pos = 0 - for (i = 1; i < linesCount; i++) { - if (_scrollMap[i] !== -1) { - pos++ - continue - } - - a = nonEmptyList[pos] - b = nonEmptyList[pos + 1] - _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a)) - } - - _scrollMap[0] = 0 - - scrollMap = _scrollMap - lineHeightMap = _lineHeightMap - - if (window.loaded && callback) callback() -} - -// sync view scroll progress to edit -let viewScrollingTimer = null - -export function syncScrollToEdit (event, preventAnimate) { - if (window.currentMode !== modeType.both || !window.syncscroll || !editArea) return - if (window.preventSyncScrollToEdit) { - if (typeof window.preventSyncScrollToEdit === 'number') { - window.preventSyncScrollToEdit-- - } else { - window.preventSyncScrollToEdit = false - } - return - } - if (!scrollMap || !lineHeightMap) { - buildMap(() => { - syncScrollToEdit(event, preventAnimate) - }) - return - } - if (editScrolling) return - - const scrollTop = viewArea[0].scrollTop - let lineIndex = 0 - for (let i = 0, l = scrollMap.length; i < l; i++) { - if (scrollMap[i] > scrollTop) { - break - } else { - lineIndex = i - } - } - let lineNo = 0 - let lineDiff = 0 - for (let i = 0, l = lineHeightMap.length; i < l; i++) { - if (lineHeightMap[i] > lineIndex) { - break - } else { - lineNo = lineHeightMap[i] - lineDiff = lineHeightMap[i + 1] - lineNo - } - } - - let posTo = 0 - let topDiffPercent = 0 - let posToNextDiff = 0 - const scrollInfo = window.editor.getScrollInfo() - const textHeight = window.editor.defaultTextHeight() - const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight - const preLastLineNo = Math.round(preLastLineHeight / textHeight) - const preLastLinePos = scrollMap[preLastLineNo] - - if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) { - posTo = preLastLineHeight - topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos) - posToNextDiff = textHeight * topDiffPercent - posTo += Math.ceil(posToNextDiff) - } else { - posTo = lineNo * textHeight - topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]) - posToNextDiff = textHeight * lineDiff * topDiffPercent - posTo += Math.ceil(posToNextDiff) - } - - if (preventAnimate) { - editArea.scrollTop(posTo) - } else { - const posDiff = Math.abs(scrollInfo.top - posTo) - var duration = posDiff / 50 - duration = duration >= 100 ? duration : 100 - editArea.stop(true, true).animate({ - scrollTop: posTo - }, duration, 'linear') - } - - viewScrolling = true - clearTimeout(viewScrollingTimer) - viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5) -} - -function viewScrollingTimeoutInner () { - viewScrolling = false -} - -// sync edit scroll progress to view -let editScrollingTimer = null - -export function syncScrollToView (event, preventAnimate) { - if (window.currentMode !== modeType.both || !window.syncscroll || !viewArea) return - if (window.preventSyncScrollToView) { - if (typeof preventSyncScrollToView === 'number') { - window.preventSyncScrollToView-- - } else { - window.preventSyncScrollToView = false - } - return - } - if (!scrollMap || !lineHeightMap) { - buildMap(() => { - syncScrollToView(event, preventAnimate) - }) - return - } - if (viewScrolling) return - - let lineNo, posTo - let topDiffPercent, posToNextDiff - const scrollInfo = window.editor.getScrollInfo() - const textHeight = window.editor.defaultTextHeight() - lineNo = Math.floor(scrollInfo.top / textHeight) - // if reach the last line, will start lerp to the bottom - const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight) - if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) { - topDiffPercent = diffToBottom / textHeight - posTo = scrollMap[lineNo + 1] - posToNextDiff = (viewBottom - posTo) * topDiffPercent - posTo += Math.floor(posToNextDiff) - } else { - topDiffPercent = (scrollInfo.top % textHeight) / textHeight - posTo = scrollMap[lineNo] - posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent - posTo += Math.floor(posToNextDiff) - } - - if (preventAnimate) { - viewArea.scrollTop(posTo) - } else { - const posDiff = Math.abs(viewArea.scrollTop() - posTo) - var duration = posDiff / 50 - duration = duration >= 100 ? duration : 100 - viewArea.stop(true, true).animate({ - scrollTop: posTo - }, duration, 'linear') - } - - editScrolling = true - clearTimeout(editScrollingTimer) - editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5) -} - -function editScrollingTimeoutInner () { - editScrolling = false -} -- cgit v1.2.3