зеркало из https://github.com/mozilla/pjs.git
Fix bug 321010 - Need better stripping of illegal css chars from category names. r=philipp, p=gekachecka
This commit is contained in:
Родитель
7f6cc7bd4f
Коммит
f10f64d1fd
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -168,12 +168,47 @@ function calendarListInitCategoryColors() {
|
|||
var categoryPrefBranch = prefService.getBranch("calendar.category.color.");
|
||||
var categories = categoryPrefBranch.getChildList("", {});
|
||||
|
||||
// check category preference name syntax
|
||||
categories = calendarConvertObsoleteColorPrefs(categoryPrefBranch, categories);
|
||||
|
||||
// Update all categories
|
||||
for each (var category in categories) {
|
||||
updateStyleSheetForObject(category, gCachedStyleSheet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove illegally formatted category names from the array coloredCategories
|
||||
* so they don't cause CSS errors. For each illegal colored category c, if
|
||||
* its color preference has not yet been replaced with a converted preference
|
||||
* with key formatStringForCSSRule(c), create the preference with the
|
||||
* converted key and with the previous preference value, and clear the old
|
||||
* preference. (For most users who upgrade and do not later add colors with a
|
||||
* downgrade version, this should convert any illegal preferences once, so
|
||||
* future runs have no illegal preferences.)
|
||||
* @param categoryPrefBranch prefBranch for "calendar.category.color."
|
||||
* @param coloredCategories array of preference name suffixes under the prefBranch.
|
||||
* @return same array with each illegal name replaced with formatted name if
|
||||
* it doesn't already exist, or simply removed from array if it does.
|
||||
*/
|
||||
function calendarConvertObsoleteColorPrefs(categoryPrefBranch, coloredCategories) {
|
||||
for (var i in coloredCategories) {
|
||||
var category = coloredCategories[i];
|
||||
if (category.search(/[^_0-9a-z-]/) != -1) {
|
||||
var categoryFix = formatStringForCSSRule(category);
|
||||
if (!categoryPrefBranch.prefHasUserValue(categoryFix)) {
|
||||
var color = categoryPrefBranch.getCharPref(category);
|
||||
categoryPrefBranch.setCharPref(categoryFix, color);
|
||||
categoryPrefBranch.clearUserPref(category); // not usable
|
||||
coloredCategories[i] = categoryFix; // replace illegal name
|
||||
} else {
|
||||
coloredCategories.splice(i, 1); // remove illegal name
|
||||
}
|
||||
}
|
||||
}
|
||||
return coloredCategories;
|
||||
}
|
||||
|
||||
function calendarListUpdateColor(aCalendar) {
|
||||
var selectorPrefix = "treechildren::-moz-tree-cell";
|
||||
|
||||
|
|
|
@ -522,14 +522,13 @@
|
|||
}
|
||||
|
||||
var categoriesSelectorList = "";
|
||||
if (itd.item.getProperty("CATEGORIES") != null) {
|
||||
var categoriesList = itd.item.getProperty("CATEGORIES").split(",");
|
||||
for (var i = 0; i < categoriesList.length; i++ ) {
|
||||
// Remove illegal chars.
|
||||
categoriesList[i] = formatStringForCSSRule(categoriesList[i]);
|
||||
}
|
||||
categoriesSelectorList = categoriesList.join(" ");
|
||||
var categoriesProperty = itd.item.getProperty("CATEGORIES");
|
||||
if (categoriesProperty) {
|
||||
var categoriesArray = categoriesStringToArray(categoriesProperty);
|
||||
var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
|
||||
categoriesSelectorList = cssClassesArray.join(" ");
|
||||
}
|
||||
|
||||
box.setAttribute("item-category", categoriesSelectorList);
|
||||
|
||||
this.dayitems.insertBefore(box, before);
|
||||
|
|
|
@ -206,13 +206,11 @@
|
|||
container.setAttribute("item-calendar", item.calendar.uri.spec);
|
||||
|
||||
var categoriesSelectorList = "";
|
||||
if (item.getProperty("CATEGORIES") != null) {
|
||||
var categoriesList = item.getProperty("CATEGORIES").split(",");
|
||||
for (var i = 0; i < categoriesList.length; i++ ) {
|
||||
// Remove illegal chars.
|
||||
categoriesList[i] = formatStringForCSSRule(categoriesList[i]);
|
||||
}
|
||||
categoriesSelectorList = categoriesList.join(" ");
|
||||
var categoriesProperty = item.getProperty("CATEGORIES");
|
||||
if (categoriesProperty) {
|
||||
var categoriesArray = categoriesStringToArray(categoriesProperty);
|
||||
var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
|
||||
categoriesSelectorList = cssClassesArray.join(" ");
|
||||
}
|
||||
|
||||
var box = document.
|
||||
|
|
|
@ -410,13 +410,18 @@ function getStyleSheet(aStyleSheetPath) {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Updates the style rules for a particular object. If the object is a
|
||||
// category (and hence doesn't have a uri), we set the border color. If
|
||||
// it's a calendar, we set the background color
|
||||
/**
|
||||
* Updates the style rules for a particular object. If the object is a
|
||||
* category (and hence doesn't have a uri), we set the category bar color.
|
||||
* If it's a calendar, we set the background color and contrasting text color.
|
||||
* @param aObject either a calendar (with a .uri), or the category color
|
||||
* pref key suffix [the non-unicode part after "calendar.category.color.",
|
||||
* equivalent to formatStringForCSSRule(categoryNameInUnicode)].
|
||||
*/
|
||||
function updateStyleSheetForObject(aObject, aSheet) {
|
||||
var selectorPrefix, name, ruleUpdaterFunc;
|
||||
if (aObject.uri) {
|
||||
// This is a calendar, so we're going to set the background color
|
||||
// For a calendar, set background and contrasting text colors
|
||||
name = aObject.uri.spec;
|
||||
selectorPrefix = "item-calendar=";
|
||||
ruleUpdaterFunc = function calendarRuleFunc(aRule, aIndex) {
|
||||
|
@ -428,12 +433,12 @@ function updateStyleSheetForObject(aObject, aSheet) {
|
|||
aRule.style.color = getContrastingTextColor(color);
|
||||
};
|
||||
} else {
|
||||
// This is a category, where we set the border. Also note that we
|
||||
// use the ~= selector, since there could be multiple categories
|
||||
name = formatStringForCSSRule(aObject);
|
||||
// For a category, set the category bar color. Also note that
|
||||
// it uses the ~= selector, since there could be multiple categories.
|
||||
name = aObject;
|
||||
selectorPrefix = "item-category~=";
|
||||
ruleUpdaterFunc = function categoryRuleFunc(aRule, aIndex) {
|
||||
var color = getPrefSafe("calendar.category.color."+aObject, null);
|
||||
var color = getPrefSafe("calendar.category.color."+name, null);
|
||||
if (color) {
|
||||
aRule.style.backgroundColor = color;
|
||||
} else {
|
||||
|
|
|
@ -67,14 +67,7 @@ var gCategoriesPane = {
|
|||
}
|
||||
|
||||
var categories = document.getElementById("calendar.categories.names").value;
|
||||
|
||||
// If no categories are configured load a default set from properties file
|
||||
if (!categories || categories == "") {
|
||||
categories = calGetString("categories", "categories");
|
||||
document.getElementById("calendar.categories.names").value = categories;
|
||||
}
|
||||
|
||||
gCategoryList = categories.split(",");
|
||||
gCategoryList = categoriesStringToArray(categories);
|
||||
|
||||
// When categories is empty, split returns an array containing one empty
|
||||
// string, rather than an empty array. This results in an empty listbox
|
||||
|
@ -87,8 +80,10 @@ var gCategoriesPane = {
|
|||
},
|
||||
|
||||
updateCategoryList: function () {
|
||||
gCategoryList.sort();
|
||||
document.getElementById("calendar.categories.names").value = gCategoryList.join(",");
|
||||
sortArrayByLocaleCollator(gCategoryList);
|
||||
document.getElementById("calendar.categories.names").value =
|
||||
categoriesArrayToString(gCategoryList);
|
||||
|
||||
var listbox = document.getElementById("categorieslist");
|
||||
|
||||
listbox.clearSelection();
|
||||
|
|
|
@ -166,12 +166,40 @@ function calendarDefaultTimezone() {
|
|||
|
||||
/**
|
||||
* Format the given string to work inside a CSS rule selector
|
||||
* (and as part of a non-unicode preference key).
|
||||
*
|
||||
* @param aString The string to format
|
||||
* @return The formatted string
|
||||
* Replaces each space ' ' char with '_'.
|
||||
* Replaces each char other than ascii digits and letters, with '-uxHHH-'
|
||||
* where HHH is unicode in hexadecimal (variable length, terminated by the '-').
|
||||
*
|
||||
* Ensures: result only contains ascii digits, letters,'-', and '_'.
|
||||
* Ensures: result is invertible, so (f(a) = f(b)) implies (a = b).
|
||||
* also means f is not idempotent, so (a != f(a)) implies (f(a) != f(f(a))).
|
||||
* Ensures: result must be lowercase.
|
||||
* Rationale: preference keys require 8bit chars, and ascii chars are legible
|
||||
* in most fonts (in case user edits PROFILE/prefs.js).
|
||||
* CSS class names in Gecko 1.8 seem to require lowercase,
|
||||
* no punctuation, and of course no spaces.
|
||||
* nmchar [_a-zA-Z0-9-]|{nonascii}|{escape}
|
||||
* name {nmchar}+
|
||||
* http://www.w3.org/TR/CSS21/grammar.html#scanner
|
||||
*
|
||||
* @param aString The unicode string to format
|
||||
* @return The formatted string using only chars [_a-zA-Z0-9-]
|
||||
*/
|
||||
function formatStringForCSSRule(aString) {
|
||||
return aString.replace(/ /g, "_").toLowerCase();
|
||||
function toReplacement(ch) {
|
||||
// char code is natural number (positive integer)
|
||||
var nat = ch.charCodeAt(0);
|
||||
switch(nat) {
|
||||
case 0x20: // space
|
||||
return "_";
|
||||
default:
|
||||
return "-ux" + nat.toString(16) + "-"; // lowercase
|
||||
}
|
||||
}
|
||||
// Result must be lowercase or style rule will not work.
|
||||
return aString.toLowerCase().replace(/[^a-zA-Z0-9]/g, toReplacement);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -979,6 +1007,80 @@ function getLocalizedPref(aPrefName, aDefault) {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of category names from preferences or locale default,
|
||||
* unescaping any commas in each category name.
|
||||
* @return array of category names
|
||||
*/
|
||||
function getPrefCategoriesArray() {
|
||||
var categories = getLocalizedPref("calendar.categories.names", null);
|
||||
// If no categories are configured load a default set from properties file
|
||||
if (!categories || categories == "") {
|
||||
categories = calGetString("categories", "categories");
|
||||
setLocalizedPref("calendar.categories.names", categories);
|
||||
}
|
||||
return categoriesStringToArray(categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert categories string to list of category names.
|
||||
*
|
||||
* Stored categories may include escaped commas within a name.
|
||||
* Split categories string at commas, but not at escaped commas (\,).
|
||||
* Afterward, replace escaped commas (\,) with commas (,) in each name.
|
||||
* @param aCategoriesPrefValue string from "calendar.categories.names" pref,
|
||||
* which may contain escaped commas (\,) in names.
|
||||
* @return list of category names
|
||||
*/
|
||||
function categoriesStringToArray(aCategories) {
|
||||
// \u001A is the unicode "SUBSTITUTE" character
|
||||
function revertCommas(name) { return name.replace(/\u001A/g, ","); }
|
||||
return aCategories.replace(/\\,/g, "\u001A").split(",").map(revertCommas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set categories preference, escaping any commas in category names.
|
||||
* @param aCategoriesArray array of category names,
|
||||
* may contain unescaped commas which will be escaped in combined pref.
|
||||
*/
|
||||
function setPrefCategoriesFromArray(aCategoriesArray) {
|
||||
setLocalizedPref("calendar.categories.names",
|
||||
categoriesArrayToString(aCategoriesList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array of category names to string.
|
||||
*
|
||||
* Category names may contain commas (,). Escape commas (\,) in each,
|
||||
* then join them in comma separated string for storage.
|
||||
* @param aSortedCategoriesArray sorted array of category names,
|
||||
* may contain unescaped commas, which will be escaped in combined string.
|
||||
*/
|
||||
function categoriesArrayToString(aSortedCategoriesArray) {
|
||||
function escapeComma(category) { return category.replace(/,/g,"\\,"); }
|
||||
return aSortedCategoriesArray.map(escapeComma).join(",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array of strings according to the current locale.
|
||||
* Modifies aStringArray, returning it sorted.
|
||||
*/
|
||||
function sortArrayByLocaleCollator(aStringArray) {
|
||||
// get a current locale string collator for compareEvents
|
||||
var localeService =
|
||||
Components
|
||||
.classes["@mozilla.org/intl/nslocaleservice;1"]
|
||||
.getService(Components.interfaces.nsILocaleService);
|
||||
var localeCollator =
|
||||
Components
|
||||
.classes["@mozilla.org/intl/collation-factory;1"]
|
||||
.getService(Components.interfaces.nsICollationFactory)
|
||||
.CreateCollation(localeService.getApplicationLocale());
|
||||
function compare(a, b) { return localeCollator.compareString(0, a, b); }
|
||||
aStringArray.sort(compare);
|
||||
return aStringArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a string in a .properties file
|
||||
*
|
||||
|
|
|
@ -335,15 +335,7 @@ function loadDialog(item) {
|
|||
}
|
||||
|
||||
// Categories
|
||||
var categoriesString = getLocalizedPref("calendar.categories.names", "");
|
||||
|
||||
// If no categories are configured load a default set from properties file
|
||||
if (!categoriesString || categoriesString == "") {
|
||||
categoriesString = calGetString("categories", "categories");
|
||||
setLocalizedPref("calendar.categories.names", categoriesString);
|
||||
}
|
||||
|
||||
var categoriesList = categoriesString.split(",");
|
||||
var categoriesList = getPrefCategoriesArray();
|
||||
|
||||
// When categoriesString is empty, split returns an array containing one
|
||||
// empty string, rather than an empty array. This results in an empty
|
||||
|
@ -353,13 +345,16 @@ function loadDialog(item) {
|
|||
}
|
||||
|
||||
// insert the category already in the menulist so it doesn't get lost
|
||||
var itemCategory = item.getProperty("CATEGORIES");
|
||||
if (itemCategory) {
|
||||
if (categoriesString.indexOf(itemCategory) == -1) {
|
||||
categoriesList[categoriesList.length] = itemCategory;
|
||||
var itemProperty = item.getProperty("CATEGORIES");
|
||||
if (itemProperty) {
|
||||
var itemCategories = categoriesStringToArray(itemProperty);
|
||||
for each (var itemCategory in itemCategories) {
|
||||
if (!categoriesList.some(function(cat){ return cat == itemCategory; })){
|
||||
categoriesList.push(itemCategory);
|
||||
}
|
||||
}
|
||||
}
|
||||
categoriesList.sort();
|
||||
sortArrayByLocaleCollator(categoriesList);
|
||||
|
||||
var oldMenulist = document.getElementById("item-categories");
|
||||
while (oldMenulist.hasChildNodes()) {
|
||||
|
@ -818,7 +813,7 @@ function saveDialog(item) {
|
|||
var category = getElementValue("item-categories");
|
||||
|
||||
if (category != "NONE") {
|
||||
setItemProperty(item, "CATEGORIES", category);
|
||||
setItemProperty(item, "CATEGORIES", categoriesArrayToString([category]));
|
||||
} else {
|
||||
item.deleteProperty("CATEGORIES");
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче