Closed curiousepic closed 4 years ago
@curiousepic, I am very sorry! Can you verify which version you are using? There was a known issue with data loss due to extension stores forcing this extension to do html sanitation, but I refactored the entire plugin to fix that issue. This should not be happening on 0.9.1.
0.9.1 is the version I have currently installed. It's possible that it only updated to this version when I relaunched chrome, but before closing chrome I checked that the last update date of the extension was July 24. I've disabled it but not uninstalled it. Thankfully, the data was still present (cached?) on my iphone app, and I edited all the items there to update them and hopefully keep them once I reload the website, though the first time this happened (thankfully with fewer items), the iphone app had already pulled the blank versions, so the loss does seem real.
@curiousepic, if you are willing, would you mind testing the extension again? If data loss is still happening I will remove the extension until I can fix the bug. And I am very sorry you hit this issue.
@ryanpcmcquen I will give it a try with some dummy data. Considering it only affected what was viewed, I'm hoping it will be safe.
@ryanpcmcquen Bad news; easily reproed
@curiousepic, I am so sorry. Thank you for reporting this. I'm working on a fix right now. If I don't have something today I will unpublish the extension.
@ryanpcmcquen Interestingly, after disabled the extension and reloading workflowy, those items were in fact restored, though some of my real items lost in the initial occurance do seem to be gone 🤔
@curiousepic, I think it's actually more of a display issue than anything, but yeah, it sucks.
@curiousepic, will you try pasting this in the console and see if you still have data loss:
/*! codeFormatter v6.1.1 by ryanpcmcquen */
//
// Ryan McQuen
/**
* @fileoverview microlight - syntax highlightning library
* @version 0.0.7
*
* @license MIT, see http://github.com/asvd/microlight
* @copyright 2016 asvd <heliosframework@gmail.com>
*
* Code structure aims at minimizing the compressed library size
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports);
} else {
factory((root.microlight = {}));
}
})(this, function (exports) {
// for better compression
var _window = window,
_document = document,
appendChild = 'appendChild',
test = 'test',
// style and color templates
textShadow = ';text-shadow:',
opacity = 'opacity:.',
_0px_0px = ' 0px 0px ',
_3px_0px_5 = '3px 0px 5',
brace = ')',
i,
microlighted,
el; // current microlighted element to run through
var reset = function (cls) {
// nodes to highlight
microlighted = _document.getElementsByClassName(cls || 'microlight');
for (i = 0; (el = microlighted[i++]); ) {
var text = el.textContent,
pos = 0, // current position
next1 = text[0], // next character
chr = 1, // current character
prev1, // previous character
prev2, // the one before the previous
token = (el.innerHTML = ''), // current token content // (and cleaning the node)
// current token type:
// 0: anything else (whitespaces / newlines)
// 1: operator or brace
// 2: closing braces (after which '/' is division not regex)
// 3: (key)word
// 4: regex
// 5: string starting with "
// 6: string starting with '
// 7: xml comment <!-- -->
// 8: multiline comment /* */
// 9: single-line comment starting with two slashes //
// 10: single-line comment starting with hash #
tokenType = 0,
// kept to determine between regex and division
lastTokenType,
// flag determining if token is multi-character
multichar,
node,
// calculating the colors for the style templates
colorArr = /(\d*\, \d*\, \d*)(, ([.\d]*))?/g.exec(
_window.getComputedStyle(el).color
),
pxColor = 'px rgba(' + colorArr[1] + ',',
alpha = colorArr[3] || 1;
// running through characters and highlighting
while (
((prev2 = prev1),
// escaping if needed (with except for comments)
// pervious character will not be therefore
// recognized as a token finalize condition
(prev1 = tokenType < 7 && prev1 == '\\' ? 1 : chr))
) {
chr = next1;
next1 = text[++pos];
multichar = token.length > 1;
// checking if current token should be finalized
if (
!chr || // end of content
// types 9-10 (single-line comments) end with a
// newline
(tokenType > 8 && chr == '\n') ||
[
// finalize conditions for other token types
// 0: whitespaces
/\S/[test](chr), // merged together
// 1: operators
1, // consist of a single character
// 2: braces
1, // consist of a single character
// 3: (key)word
!/[$\w]/[test](chr),
// 4: regex
(prev1 == '/' || prev1 == '\n') && multichar,
// 5: string with "
prev1 == '"' && multichar,
// 6: string with '
prev1 == "'" && multichar,
// 7: xml comment
text[pos - 4] + prev2 + prev1 == '-->',
// 8: multiline comment
prev2 + prev1 == '*/',
][tokenType]
) {
// appending the token to the result
if (token) {
// remapping token type into style
// (some types are highlighted similarly)
el[appendChild](
(node = _document.createElement('span'))
).setAttribute(
'style',
[
// 0: not formatted
'',
// 1: keywords
textShadow +
_0px_0px +
9 +
pxColor +
alpha * 0.7 +
'),' +
_0px_0px +
2 +
pxColor +
alpha * 0.4 +
brace,
// 2: punctuation
opacity +
6 +
textShadow +
_0px_0px +
7 +
pxColor +
alpha / 4 +
'),' +
_0px_0px +
3 +
pxColor +
alpha / 4 +
brace,
// 3: strings and regexps
opacity +
7 +
textShadow +
_3px_0px_5 +
pxColor +
alpha / 5 +
'),-' +
_3px_0px_5 +
pxColor +
alpha / 5 +
brace,
// 4: comments
'font-style:italic;' +
opacity +
5 +
textShadow +
_3px_0px_5 +
pxColor +
alpha / 4 +
'),-' +
_3px_0px_5 +
pxColor +
alpha / 4 +
brace,
][
// not formatted
!tokenType
? 0
: // punctuation
tokenType < 3
? 2
: // comments
tokenType > 6
? 4
: // regex and strings
tokenType > 3
? 3
: // otherwise tokenType == 3, (key)word
// (1 if regexp matches, 0 otherwise)
+/^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/[
test
](token)
]
);
node[appendChild](_document.createTextNode(token));
}
// saving the previous token type
// (skipping whitespaces and comments)
lastTokenType =
tokenType && tokenType < 7 ? tokenType : lastTokenType;
// initializing a new token
token = '';
// determining the new token type (going up the
// list until matching a token type start
// condition)
tokenType = 11;
while (
![
1, // 0: whitespace
// 1: operator or braces
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr),
/[\])]/[test](chr), // 2: closing brace
/[$\w]/[test](chr), // 3: (key)word
chr == '/' && // 4: regex
// previous token was an
// opening brace or an
// operator (otherwise
// division, not a regex)
lastTokenType < 2 &&
// workaround for xml
// closing tags
prev1 != '<',
chr == '"', // 5: string with "
chr == "'", // 6: string with '
// 7: xml comment
chr + next1 + text[pos + 1] + text[pos + 2] ==
'<!--',
chr + next1 == '/*', // 8: multiline comment
chr + next1 == '//', // 9: single-line comment
chr == '#', // 10: hash-style comment
][--tokenType]
);
}
token += chr;
}
}
};
exports.reset = reset;
if (_document.readyState == 'complete') {
reset();
} else {
_window.addEventListener(
'load',
function () {
reset();
},
0
);
}
});
/*jslint browser*/
(function () {
'use strict';
var replacement = function (matchedText, blockType, opacity, language) {
opacity = opacity || 1;
language = language === undefined ? 'plain' : language;
var block = document.createElement(blockType);
var textNode = document.createTextNode(String(matchedText));
if (language !== 'plain' && language !== 'p') {
block.classList.add('microlight');
}
block.appendChild(textNode);
block.style.opacity = opacity;
return block;
};
var backtickOpacity = 0.3;
var codeFormatter = function (selector) {
var contentArray = Array.prototype.slice.call(
document.querySelectorAll(selector)
);
// Multi-line code:
var tripleTickRegex = /```[\w\W]+```/gim;
// Inline code:
var singleTickRegex = /`[^`]+`/g;
var codeLanguageRegex = /```{1}.*/;
var tripleTickCapture = /(```)/;
var singleTickCapture = /(`[^`]+`)/;
contentArray.forEach(function (content) {
if (
!/<pre/gi.test(content.innerHTML) &&
!/<code/gi.test(content.innerHTML) &&
content.textContent
) {
var theNewKidsOnTheBlock = [];
if (tripleTickRegex.test(content.textContent)) {
// This needs a little extra filtering,
// but cascading is cool.
var codeLanguage = String(
content.textContent.match(codeLanguageRegex)
)
.slice(3)
.split(/(\s+)/)[0]
.trim();
var pairs = 0;
content.textContent
.split(tripleTickCapture)
.forEach(function (textBlock, index, self) {
if (tripleTickCapture.test(textBlock)) {
pairs++;
theNewKidsOnTheBlock.push(
replacement(
textBlock,
'pre',
backtickOpacity
)
);
} else if (
self[index - 1] === '```' &&
self[index + 1] === '```' &&
pairs % 2 === 1
) {
theNewKidsOnTheBlock.push(
replacement(
textBlock,
'pre',
1,
textBlock.slice(0, 1) === 'p'
? 'plain'
: ''
)
);
} else {
theNewKidsOnTheBlock.push(
replacement(textBlock, 'span')
);
}
});
}
if (singleTickRegex.test(content.textContent)) {
var singlePairs = 0;
var singlePlayerGame = function (splitBlock, index, self) {
if (singleTickCapture.test(splitBlock)) {
singlePairs++;
return replacement(
splitBlock,
'code',
backtickOpacity
);
} else if (
self[index - 1] === '`' &&
self[index + 1] === '`' &&
singlePairs % 2 === 1
) {
return replacement(splitBlock, 'code');
} else {
return replacement(splitBlock, 'span');
}
};
if (theNewKidsOnTheBlock.length > 0) {
theNewKidsOnTheBlock.forEach(function (
block,
index,
self
) {
if (singleTickCapture.test(block.textContent)) {
var newSet = [];
block.textContent
.split(singleTickCapture)
.forEach(function (child) {
if (singleTickRegex.test(child)) {
newSet.push(
replacement(child, 'code')
);
} else {
newSet.push(
replacement(child, 'span')
);
}
});
self[index] = newSet;
}
});
theNewKidsOnTheBlock = theNewKidsOnTheBlock.flat();
} else {
var theSingleKids = [];
theSingleKids = content.textContent
.split(singleTickCapture)
.map(singlePlayerGame);
var inlineSpan = document.createElement('span');
theSingleKids.forEach(function (singleKid) {
inlineSpan.appendChild(singleKid);
});
theNewKidsOnTheBlock.push(inlineSpan);
}
}
if (theNewKidsOnTheBlock.length > 0) {
while (content.firstChild) {
content.removeChild(content.firstChild);
}
theNewKidsOnTheBlock.forEach(function (newKid) {
content.appendChild(newKid);
});
}
}
});
// Invoke microlight:
microlight.reset();
};
// Attach globally:
window.codeFormatter = codeFormatter;
})();
codeFormatter('.content[contenteditable]');
Note that you will want the extension to still be disabled.
@ryanpcmcquen Appears to work properly with a similar test
I'm updating the Firefox and Chrome stores for Workflowy and The Google Keep code formatters now. Relevant release: https://github.com/ryanpcmcquen/codeFormatter/releases/tag/6.1.1
I've submitted updated versions for Chrome and Firefox of this and the Google Keep version with the following note for the reviewers:
This version fixes a CRITICAL BUG that caused data loss. Please publish it as soon as possible.
Firefox updated the same day but I am still waiting on Google to approve the new version.
The Chrome store has been updated as well.
According to Workflowy, this extension is causing data loss. I've used the extension for years, but this is the first occurance of this issue, so may be due to the July 24 update. https://twitter.com/WorkFlowy/status/1290372459837808641