From f50061981ae7bb566991cbf0ed22b4883a91bb48 Mon Sep 17 00:00:00 2001 From: 0xlay Date: Fri, 13 Aug 2021 23:31:19 +0300 Subject: [PATCH] Add support DarkMode, function list & auto-completion for TypeScript Close #10381 --- .../Test/FunctionList/typescript/unitTest | 364 ++++++++++++++++++ .../typescript/unitTest.expected.result | 1 + PowerEditor/installer/APIs/typescript.xml | 72 ++++ .../installer/functionList/typescript.xml | 32 ++ .../installer/themes/DarkModeDefault.xml | 19 + PowerEditor/src/langs.model.xml | 4 +- 6 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 PowerEditor/Test/FunctionList/typescript/unitTest create mode 100644 PowerEditor/Test/FunctionList/typescript/unitTest.expected.result create mode 100644 PowerEditor/installer/APIs/typescript.xml create mode 100644 PowerEditor/installer/functionList/typescript.xml diff --git a/PowerEditor/Test/FunctionList/typescript/unitTest b/PowerEditor/Test/FunctionList/typescript/unitTest new file mode 100644 index 000000000..e1b3d341c --- /dev/null +++ b/PowerEditor/Test/FunctionList/typescript/unitTest @@ -0,0 +1,364 @@ +var crypto = require('crypto'), + Friends, + User, + Post, + WallPost, + Comment, + LoginToken; + +function extractKeywords(text) { + if (!text) return []; + + return text. + split(/\s+/). + filter(function(v) { return v.length > 2; }). + filter(function(v, i, a) { return a.lastIndexOf(v) === i; }); +} + + +function convertBasicMarkup(input, allowHtml) { + var strongRe = /[*]{2}([^*]+)[*]{2}/gm; + var emRe = /[*]{1}([^*]+)[*]{1}/gm; + var linkRe = /\[([^\]]*)\]\(([^\)]*?)\)/gm; + var nlRe = /\r\n/gm; + var crRe = /\r/gm; + + // special re's to revert linebreaks from
+ var codeRe = /(]*>(.*?)<\/code>)/gm; + + // cleanup newlines + input = input.replace(nlRe, "\n"); + input = input.replace(crRe, "\n"); + + // strip existing html before inserting breaks/markup + if (!allowHtml) { + // strip html + input = input + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + // convert newlines to breaks + input = input.replace(/\n/gm, '
'); + + // replace basic markup + input = input.replace(strongRe, function(whole, m1, m2, m3) { + return '' + m1 + ''; + }); + + input = input.replace(emRe, function(whole, m1, m2, m3) { + return '' + m1 + ''; + }); + + input = input.replace(linkRe, function(whole, m1, m2) { + // fix up protocol + if (!m2.match(/(http(s?)|ftp(s?)):\/\//gm)) + // prepend http as default + m2 = 'http://' + m2; + return '' + m1 + ''; + }); + + // revert code blocks + input = input.replace(codeRe, function(whole, m1) { + return m1.replace(/
/gm, '\n'); + }); + + return input; +} + + + + +function defineModels(mongoose, fn) { + var Schema = mongoose.Schema, + ObjectId = Schema.ObjectId; + + + /** + * Comment model + * + * Used for persisting user comments + */ + var Comment = new Schema({ + user_id: ObjectId, + //photo:String, + date: Date, + body: String, + post_id:ObjectId, + }); + + // register virtual members + Comment.virtual('readableday') + .get(function() { + var day = this.date.getDate(); + return (day < 10 ? '0' + day : day); + }); + + Comment.virtual('readablemonth') + .get(function() { + return monthNamesShort[this.date.getMonth()]; + }); + + Comment.virtual('readabletime') + .get(function() { + var hour = this.date.getHours(); + var minute = this.date.getMinutes(); + return (hour < 10 ? '0' + hour : hour) + ':' + (minute < 10 ? '0' + minute : minute); + }); + + Comment.virtual('bodyParsed') + .get(function() { + return convertBasicMarkup(this.body, false); + }); + + // register validators + /*Comment.path('author').validate(function(val) { + return val.length > 0; + }, 'AUTHOR_MISSING');*/ + + Comment.path('body').validate(function(val) { + return val.length > 0; + }, 'BODY_MISSING'); + + +/** + * Model: WallPost + */ + + +var WallPost = new Schema({ + friend_id: String, + preview: String, + body: String, + //rsstext: String, + slug: String, + created: Date, + modified: Date, + //tags: [String], + user_id:ObjectId, + posted_on_user_id : ObjectId, + //comments: [Comment] + }); + + var monthNames = [ 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', + 'August', 'September', 'Oktober', 'November', 'Dezember' ]; + var monthNamesShort = [ 'Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', + 'Aug', 'Sep', 'Okt', 'Nov', 'Dez' ]; + + // define virtual getter method for id (readable string) + WallPost.virtual('id') + .get(function() { + return this._id.toHexString(); + }); + + WallPost.virtual('url') + .get(function() { + // build url for current post + var year = this.created.getFullYear(); + var month = this.created.getMonth() + 1; + var day = this.created.getDate(); + return '/' + year + '/' + (month < 10 ? '0' + month : month) + '/' + (day < 10 ? '0' + day : day) + '/' + this.slug + '/'; + }); + + WallPost.virtual('rfc822created') + .get(function() { + return this.created.toGMTString(); + }); + + WallPost.virtual('readabledate') + .get(function() { + var year = this.created.getFullYear(); + var month = monthNames[this.created.getMonth()]; + var day = this.created.getDate(); + return (day < 10 ? '0' + day : day) + '. ' + month + ' ' + year; + }); + + WallPost.virtual('readableday') + .get(function() { + var day = this.created.getDate(); + return (day < 10 ? '0' + day : day); + }); + + WallPost.virtual('readablemonth') + .get(function() { + return monthNamesShort[this.created.getMonth()]; + }); + + WallPost.virtual('previewParsed') + .get(function() { + return convertBasicMarkup(this.preview, true); + }); + + WallPost.virtual('bodyParsed') + .get(function() { + return convertBasicMarkup(this.body, true); + }); + + // register validators + /*WallPost.path('title').validate(function(val) { + return val.length > 0; + }, 'TITLE_MISSING'); + + WallPost.path('preview').validate(function(val) { + return val.length > 0; + }, 'PREVIEW_MISSING'); + + WallPost.path('rsstext').validate(function(val) { + return val.length > 0; + }, 'RSSTEXT_MISSING');*/ + + WallPost.path('body').validate(function(val) { + return val.length > 0; + }, 'BODY_MISSING'); + + // generate a proper slug value for Wallpost + function slugGenerator (options){ + options = options || {}; + var key = options.key || 'body'; + return function slugGenerator(schema){ + schema.path(key).set(function(v){ + this.slug = v.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/\++/g, ''); + return v; + }); + }; + }; + + // attach slugGenerator plugin to Wallpost schema + WallPost.plugin(slugGenerator()); + + + +/** + * Model: User + */ +function validatePresenceOf(value) { + return value && value.length; +} +var User = new Schema({ + 'first_name': { type: String, validate: /[a-z]/ }, + 'last_name':{ type: String, validate: /[a-z]/ }, + 'age':Number, + 'sex':{ type: String}, + 'photo':String, + 'location':{ type: String, validate: /[a-z]/ }, + 'latitude' : String, + 'longitude' : String, + 'keywords': [String], + 'username':String, + 'email': { type: String, validate: [validatePresenceOf, 'an email is required'], index: { unique: true }, required:true }, + 'hashed_password': { type: String}, + 'salt': String, + }); + + User.virtual('id') + .get(function() { + return this._id.toHexString(); + }); + + User.virtual('password') + .set(function(password) { + this._password = password; + this.salt = this.makeSalt(); + this.hashed_password = this.encryptPassword(password); + }) + .get(function() { return this._password; }); + + User.method('authenticate', function(plainText) { + return this.encryptPassword(plainText) === this.hashed_password; + }); + + User.method('makeSalt', function() { + return Math.round((new Date().valueOf() * Math.random())) + ''; + }); + + User.method('encryptPassword', function(password) { + return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); + }); + + User.pre('save', function(next) { + this.keywords = extractKeywords(this.first_name); + next(); + if (!validatePresenceOf(this.password)) { + next(new Error('Invalid password')); + } else { + next(); + } + + }); + + +var Friends = new Schema({ + requestor : String + , acceptor : String + , date_requested : Date + , status:Number +}); + +Friends.virtual('id') + .get(function() { + return this._id.toHexString(); + }); + +var Post = new Schema({ + filename : { type: String, index: true } + , file : String + , created_at : Date + , user_id: ObjectId +}); + +Post.virtual('id') + .get(function() { + return this._id.toHexString(); + }); + +/** + * Model: LoginToken + * + * Used for session persistence. + */ +var LoginToken = new Schema({ + email: { type: String, index: true }, + series: { type: String, index: true }, + token: { type: String, index: true } + }); + + LoginToken.method('randomToken', function() { + return Math.round((new Date().valueOf() * Math.random())) + ''; + }); + + LoginToken.pre('save', function(next) { + // Automatically create the tokens + this.token = this.randomToken(); + + if (this.isNew) + this.series = this.randomToken(); + + next(); + }); + + LoginToken.virtual('id') + .get(function() { + return this._id.toHexString(); + }); + + LoginToken.virtual('cookieValue') + .get(function() { + return JSON.stringify({ email: this.email, token: this.token, series: this.series }); + }); + + + + mongoose.model('User', User); + mongoose.model('Post', Post); + mongoose.model('Friends', Friends); + mongoose.model('LoginToken', LoginToken); + mongoose.model('WallPost', WallPost); + mongoose.model('Comment', Comment); + fn(); +} + +exports.defineModels = defineModels; + \ No newline at end of file diff --git a/PowerEditor/Test/FunctionList/typescript/unitTest.expected.result b/PowerEditor/Test/FunctionList/typescript/unitTest.expected.result new file mode 100644 index 000000000..297c70adc --- /dev/null +++ b/PowerEditor/Test/FunctionList/typescript/unitTest.expected.result @@ -0,0 +1 @@ +{"leaves":["extractKeywords","convertBasicMarkup","function","function","function","function","defineModels","slugGenerator","slugGenerator","validatePresenceOf","function","function","function","function","function","function"],"root":"unitTest"} \ No newline at end of file diff --git a/PowerEditor/installer/APIs/typescript.xml b/PowerEditor/installer/APIs/typescript.xml new file mode 100644 index 000000000..6abc9d1b1 --- /dev/null +++ b/PowerEditor/installer/APIs/typescript.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PowerEditor/installer/functionList/typescript.xml b/PowerEditor/installer/functionList/typescript.xml new file mode 100644 index 000000000..5fc1c3eda --- /dev/null +++ b/PowerEditor/installer/functionList/typescript.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PowerEditor/installer/themes/DarkModeDefault.xml b/PowerEditor/installer/themes/DarkModeDefault.xml index 9d54812e5..39e0b107a 100644 --- a/PowerEditor/installer/themes/DarkModeDefault.xml +++ b/PowerEditor/installer/themes/DarkModeDefault.xml @@ -1244,6 +1244,25 @@ License: GPL2 + + + + + + + + + + + + + + + + + + + diff --git a/PowerEditor/src/langs.model.xml b/PowerEditor/src/langs.model.xml index 33a3b6656..9cb7bc5ed 100644 --- a/PowerEditor/src/langs.model.xml +++ b/PowerEditor/src/langs.model.xml @@ -372,8 +372,8 @@ - abstract package symbol get module require set as declare type null NaN undefined from async await break case catch class const continue debugger default delete do else enum export extends finally for function goto if implements import in instanceof int interface let new of private protected public return static super switch synchronized this throw throws transient try typeof var volatile while with true false prototype yield - Array String number string boolean void any bigint Date + abstract package symbol get module require set as declare type null NaN undefined from async await break case catch class const continue debugger default delete do else enum export extends finally for function if implements import in instanceof interface let new of private protected public return static super switch synchronized this throw try typeof var while with true false prototype yield + number string boolean void any bigint accept_on alias always always_comb always_ff always_latch and assert assign assume attribute automatic before begin bind bins binsof bit break buf bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable dist do edge else end endattribute endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup endinterface endmodule endpackage endprimitive endprogram endproperty endsequence endspecify endtable endtask enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin function generate genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import incdir include initial inout input inside instance int integer interconnect interface intersect join join_any join_none large let liblist library local localparam logic longint macromodule matches medium modport module nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 null or output package packed parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 scalared sequence shortint shortreal showcancelled signed small soft solve specify specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on s_always s_eventually s_nexttime s_until s_until_with table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior trireg type typedef union unique unique0 unsigned until until_with untyped use var vectored virtual void wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor