Skip to content

Commit

Permalink
Merge pull request #738 from owncloud/vcard-within-repo
Browse files Browse the repository at this point in the history
Use vCard from the app itself
  • Loading branch information
phil-davis committed Jun 10, 2022
2 parents 2825dab + 91487fb commit 265274f
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 9 deletions.
4 changes: 2 additions & 2 deletions .eslintrc.json
Expand Up @@ -25,12 +25,12 @@
},
"globals": {
"angular": false,
"vCard": false,
"_": false,
"OC": false,
"t": false,
"n": false,
"dav": false,
"OCA": false
"OCA": false,
"vCard": false
}
}
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -29,3 +29,6 @@ node_modules/

# drone CI is in .drone.star, do not let someone accidentally commit a local .drone.yml
.drone.yml
# IDE Caches
.idea/
.vscode/
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -170,6 +170,7 @@ appstore: build
README.md \
js/public \
js/dav/dav.js \
js/vcard/vcard.js \
js/vendor/angular/angular.js \
js/vendor/angular-bootstrap/ui-bootstrap.min.js \
js/vendor/angular-bootstrap/ui-bootstrap-tpls.min.js \
Expand All @@ -181,7 +182,6 @@ appstore: build
js/vendor/jquery-timepicker/jquery.ui.timepicker.js \
js/vendor/ngclipboard/dist/ngclipboard.min.js \
js/vendor/ui-select/dist/select.js \
js/vendor/vcard/src/vcard.js \
$(appstore_package_name)

ifdef CAN_SIGN
Expand Down
3 changes: 1 addition & 2 deletions bower.json
Expand Up @@ -26,8 +26,7 @@
"angular-uuid4": "0.3.1",
"jquery-timepicker": "883bb2cd94",
"ui-select": "angular-ui/ui-select#0.14.9",
"ngclipboard": "^1.1.1",
"vcard": "0.2.7"
"ngclipboard": "^1.1.1"
},
"devDependencies": {
"angular-mocks": "1.5.8",
Expand Down
8 changes: 5 additions & 3 deletions gulpfile.js
Expand Up @@ -20,7 +20,8 @@ gulp.task('eslint', function() {
'js/components/**/*.js',
'js/models/**/*.js',
'js/services/**/*.js',
'js/filters/**/*.js'
'js/filters/**/*.js',
'js/vcard/vcard.js'
])
.pipe(eslint())
.pipe(eslint.format())
Expand All @@ -34,7 +35,8 @@ gulp.task('default', gulp.series('eslint', function() {
'js/components/**/*.js',
'js/models/**/*.js',
'js/services/**/*.js',
'js/filters/**/*.js'
'js/filters/**/*.js',
'js/vcard/vcard.js'
])
// concat (+sourcemaps)
.pipe(sourcemaps.init())
Expand All @@ -47,4 +49,4 @@ gulp.task('default', gulp.series('eslint', function() {

gulp.task('watch', gulp.series('default', function() {
gulp.watch(['js/**/*.js', '!js/public/**/*.js'], gulp.series('default'));
}));
}));
303 changes: 303 additions & 0 deletions js/vcard/vcard.js
@@ -0,0 +1,303 @@
var vCard = (function () {
var PREFIX = 'BEGIN:VCARD',
POSTFIX = 'END:VCARD';

/**
* Return json representation of vCard
* @param {string} string raw vCard
* @returns {*}
*/
function parse(string) {
var result = {},
lines = string.split(/\r\n|\r|\n/),
count = lines.length,
pieces,
key,
value,
meta,
namespace;

for (var i = 0; i < count; i++) {
if (lines[i] == '') {
continue;
}
if (lines[i].toUpperCase() == PREFIX || lines[i].toUpperCase() == POSTFIX) {
continue;
}
var data = lines[i];

/**
* Check that next line continues current
* @param {number} i
* @returns {boolean}
*/
var isValueContinued = function (i) {
return i + 1 < count && (lines[i + 1][0] == ' ' || lines[i + 1][0] == '\t');
};
// handle multiline properties (i.e. photo).
// next line should start with space or tab character
if (isValueContinued(i)) {
while (isValueContinued(i)) {
data += lines[i + 1].trim();
i++;
}
}

pieces = data.split(':');
key = pieces.shift();
value = pieces.join(':');
namespace = false;
meta = {};

// meta fields in property
if (key.match(/;/)) {
key = key
.replace(/\\;/g, 'ΩΩΩ')
.replace(/\\,/, ',');
var metaArr = key.split(';').map(function (item) {
return item.replace(/ΩΩΩ/g, ';');
});
key = metaArr.shift();
metaArr.forEach(function (item) {
var arr = item.split('=');
arr[0] = arr[0].toLowerCase();
if (arr[0].length === 0) {
return;
}
if (meta[arr[0]]) {
meta[arr[0]].push(arr[1]);
} else {
meta[arr[0]] = [arr[1]];
}
});
}

// values with \n
value = value
.replace(/\\n/g, '\n');

value = tryToSplit(value);

// Grouped properties
if (key.match(/\./)) {
var arr = key.split('.');
key = arr[1];
namespace = arr[0];
}

var newValue = {
value: value
};
if (Object.keys(meta).length) {
newValue.meta = meta;
}
if (namespace) {
newValue.namespace = namespace;
}

if (key.indexOf('X-') !== 0) {
key = key.toLowerCase();
}

if (typeof result[key] === 'undefined') {
result[key] = [newValue];
} else {
result[key].push(newValue);
}

}

return result;
}

var HAS_SEMICOLON_SEPARATOR = /[^\\];|^;/,
HAS_COMMA_SEPARATOR = /[^\\],|^,/;
/**
* Split value by "," or ";" and remove escape sequences for this separators
* @param {string} value
* @returns {string|string[]
*/
function tryToSplit(value) {
if (value.match(HAS_SEMICOLON_SEPARATOR)) {
value = value.replace(/\\,/g, ',');
return splitValue(value, ';');
} else if (value.match(HAS_COMMA_SEPARATOR)) {
value = value.replace(/\\;/g, ';');
return splitValue(value, ',');
} else {
return value
.replace(/\\,/g, ',')
.replace(/\\;/g, ';');
}
}
/**
* Split vcard field value by separator
* @param {string|string[]} value
* @param {string} separator
* @returns {string|string[]}
*/
function splitValue(value, separator) {
var separatorRegexp = new RegExp(separator);
var escapedSeparatorRegexp = new RegExp('\\\\' + separator, 'g');
// easiest way, replace it with really rare character sequence
value = value.replace(escapedSeparatorRegexp, 'ΩΩΩ');
if (value.match(separatorRegexp)) {
value = value.split(separator);

value = value.map(function (item) {
return item.replace(/ΩΩΩ/g, separator);
});
} else {
value = value.replace(/ΩΩΩ/g, separator);
}
return value;
}

var guid = (function() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return function() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
};
})();

var COMMA_SEPARATED_FIELDS = ['nickname', 'related', 'categories', 'pid'];

var REQUIRED_FIELDS = ['fn'];

/**
* Generate vCard representation af object
* @param {*} data
* @param {boolean=} addRequired determine if generator should add required properties (version and uid)
* @returns {string}
*/
function generate(data, addRequired) {
var lines = [PREFIX],
line = '';

if (addRequired && !data.version) {
data.version = [{value: '3.0'}];
}
if (addRequired && !data.uid) {
data.uid = [{value: guid()}];
}

var escapeCharacters = function (v) {
if (typeof v === 'undefined') {
return '';
}
return v
.replace(/\n/g, '\\n')
.replace(/;/g, '\\;')
.replace(/,/g, '\\,');
};

var escapeTypeCharacters = function(v) {
if (typeof v === 'undefined') {
return '';
}
return v
.replace(/\n/g, '\\n')
.replace(/;/g, '\\;');
};

Object.keys(data).forEach(function (key) {
if (!data[key] || typeof data[key].forEach !== 'function') {
return;
}
data[key].forEach(function (value) {
// ignore undefined values
if (typeof value.value === 'undefined') {
return;
}

// ignore empty values (unless it's a required field)
if (value.value == '' && REQUIRED_FIELDS.indexOf(key) === -1) {
return;
}

// ignore empty array values
if (value.value instanceof Array) {
var empty = true;
for (var i = 0; i < value.value.length; i++) {
if (typeof value.value[i] !== 'undefined' && value.value[i] != '') {
empty = false;
break;
}
}
if (empty) {
return;
}
}
line = '';

// add namespace if exists
if (value.namespace) {
line += value.namespace + '.';
}
line += key.indexOf('X-') === 0 ? key : key.toUpperCase();

// add meta properties
if (typeof value.meta === 'object') {
Object.keys(value.meta).forEach(function (metaKey) {
// values of meta tags must be an array
if (typeof value.meta[metaKey].forEach !== 'function') {
return;
}
value.meta[metaKey].forEach(function (metaValue) {
if (metaKey.length > 0) {
if (metaKey.toUpperCase() === 'TYPE') {
// Do not escape the comma when it is the type property. This breaks a lot.
line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeTypeCharacters(metaValue);
} else {
line += ';' + escapeCharacters(metaKey.toUpperCase()) + '=' + escapeCharacters(metaValue);
}
}
});
});
}

line += ':';



if (typeof value.value === 'string') {
line += escapeCharacters(value.value);
} else {
// list-values
var separator = COMMA_SEPARATED_FIELDS.indexOf(key) !== -1
? ','
: ';';
line += value.value.map(function (item) {
return escapeCharacters(item);
}).join(separator);
}

// line-length limit. Content lines
// SHOULD be folded to a maximum width of 75 octets, excluding the line break.
if (line.length > 75) {
var firstChunk = line.substr(0, 75),
least = line.substr(75);
var splitted = least.match(/.{1,74}/g);
lines.push(firstChunk);
splitted.forEach(function (chunk) {
lines.push(' ' + chunk);
});
} else {
lines.push(line);
}
});
});

lines.push(POSTFIX);
return lines.join('\r\n');
}
return {
parse: parse,
generate: generate
};
})();
2 changes: 1 addition & 1 deletion karma.conf.js
Expand Up @@ -20,7 +20,7 @@ module.exports = function(config) {
'js/vendor/ui-select/dist/select.js',

'js/dav/dav.js',
'js/vendor/vcard/src/vcard.js',
'js/vcard/vcard.js',

'js/vendor/underscore/underscore-umd.js',
'js/vendor/angular-mocks/angular-mocks.js',
Expand Down

0 comments on commit 265274f

Please sign in to comment.