Merge remote-tracking branch 'origin/devel' into lowercase-usernames

Conflicts:
	.meteor/packages
	.meteor/release
	.meteor/versions
	lib/config/at_config.js
This commit is contained in:
Luca Mussi 2015-03-12 09:40:05 +01:00
commit f46efc1255
240 changed files with 5041 additions and 1947 deletions

View file

@ -4,20 +4,16 @@ root = true
[*.{js,html}]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 80
end_of_line = lf
indent_brace_style = 1TBS
spaces_around_operators = true
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
quote_type = auto
spaces_around_operators = true
trim_trailing_whitespace = true
[*.md]

133
.jshintrc Normal file
View file

@ -0,0 +1,133 @@
//.jshintrc
{
// JSHint Meteor Configuration File
// Match the Meteor Style Guide
//
// By @raix with contributions from @aldeed and @awatson1978
// Source https://github.com/raix/Meteor-jshintrc
//
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : false, // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
"trailing" : true, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements"
"globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jasmine" : true, // Jasmine testing framework
"jquery" : false, // jQuery
"mootools" : false, // MooTools
"node" : false, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
//"meteor" : false, // Meteor.js
// Legacy
"nomen" : false, // true: Prohibit dangling `_` in variables
"onevar" : false, // true: Allow only one `var` statement per function
"passfail" : false, // true: Stop on first error
"white" : false, // true: Check against strict whitespace and indentation rules
// Custom globals, from http://docs.meteor.com, in the order they appear there
"globals" : {
"Meteor": false,
"DDP": false,
"Mongo": false, //Meteor.Collection renamed to Mongo.Collection
"Session": false,
"Accounts": false,
"Template": false,
"Blaze": false, //UI is being renamed Blaze
"UI": false,
"Match": false,
"check": false,
"Tracker": false, //Deps renamed to Tracker
"Deps": false,
"ReactiveVar": false,
"EJSON": false,
"HTTP": false,
"Email": false,
"Assets": false,
"Handlebars": false, // https://github.com/meteor/meteor/wiki/Handlebars
"Package": false,
// Meteor internals
"DDPServer": false,
"global": false,
"Log": false,
"MongoInternals": false,
"process": false,
"WebApp": false,
"WebAppInternals": false,
// globals useful when creating Meteor packages
"Npm": false,
"Tinytest": false,
// common Meteor packages
"Random": false,
"_": false, // Underscore.js
"$": false, // jQuery
"Router": false // iron-router
}
}

View file

@ -26,13 +26,12 @@ meteorhacks:fast-render@2.1.5
meteorhacks:subs-manager@1.2.2
meteorhacks:npm
aldeed:autoform
aldeed:collection2@2.3.0
aldeed:simple-schema@1.2.0
aldeed:autoform@5.0.0
aldeed:collection2
aldeed:simple-schema
mrt:jquery-hotkeys
mrt:cookies
mrt:mailchimp
ccan:cssreset # CSS reset (Must come before any other css)
cmather:handlebars-server
@ -48,7 +47,8 @@ bengott:avatar
jparker:gravatar
tap:i18n@1.2.1
tsega:bootstrap3-datetimepicker@3.1.3_1
momentjs:moment@2.8.4
momentjs:moment
aslagle:reactive-table
# Testing
@ -83,6 +83,8 @@ telescope-invites
telescope-post-by-feed
telescope-releases
telescope-getting-started
telescope-subscribe-to-posts
telescope-tagline-banner
# Custom Packages
useraccounts:unstyled@1.6.1
useraccounts:unstyled@1.8.1

View file

@ -1 +1 @@
METEOR@1.0.3.1
METEOR@1.0.3.2

View file

@ -5,15 +5,16 @@ accounts-password@1.0.6
accounts-twitter@1.0.3
accounts-ui@1.1.4
accounts-ui-unstyled@1.1.6
aldeed:autoform@4.2.2
aldeed:collection2@2.3.1
aldeed:simple-schema@1.3.0
aldeed:autoform@5.0.0
aldeed:collection2@2.3.2
aldeed:simple-schema@1.3.1
anti:i18n@0.4.3
application-configuration@1.0.4
artwells:queue@0.0.3
aslagle:reactive-table@0.6.15
autoupdate@1.1.5
backbone@1.0.0
base64@1.0.2
bengott:avatar@0.7.3
bengott:avatar@0.7.5
binary-heap@1.0.2
blaze@2.0.4
blaze-tools@1.0.2
@ -28,7 +29,7 @@ cmather:handlebars-server@2.0.0
coffeescript@1.0.5
ddp@1.0.14
deps@1.0.6
djedi:sanitize-html@1.3.0
djedi:sanitize-html@1.6.1
ejson@1.0.5
email@1.0.5
facebook@1.1.3
@ -54,34 +55,33 @@ jparker:crypto-md5@0.1.1
jparker:gravatar@0.3.1
jquery@1.11.3
json@1.0.2
kestanous:herald@1.1.3
kestanous:herald@1.3.0
kestanous:herald-email@0.4.2
launch-screen@1.0.1
less@1.0.12
livedata@1.0.12
localstorage@1.0.2
logging@1.0.6
matb33:collection-hooks@0.7.9
matb33:collection-hooks@0.7.11
meteor@1.1.4
meteor-platform@1.2.1
meteorhacks:async@1.0.0
meteorhacks:fast-render@2.3.0
meteorhacks:inject-data@1.2.1
meteorhacks:kadira@2.15.1
meteorhacks:kadira-binary-deps@1.3.1
meteorhacks:meteorx@1.2.1
meteorhacks:fast-render@2.3.1
meteorhacks:inject-data@1.2.2
meteorhacks:kadira@2.18.3
meteorhacks:meteorx@1.3.1
meteorhacks:npm@1.2.2
meteorhacks:picker@1.0.1
meteorhacks:subs-manager@1.2.4
meteorhacks:subs-manager@1.3.0
minifiers@1.1.3
minimongo@1.0.6
miro:mailchimp@1.0.2
mobile-status-bar@1.0.2
momentjs:moment@2.9.0
mongo@1.0.11
mongo-livedata@1.0.7
mrt:cookies@0.3.0
mrt:jquery-hotkeys@0.0.1
mrt:mailchimp@0.4.0
mrt:moment@2.8.1
npm-bcrypt@0.7.7
npm-container@1.0.0
@ -91,14 +91,14 @@ oauth2@1.1.2
observe-sequence@1.0.4
ordered-dict@1.0.2
percolatestudio:synced-cron@1.1.0
rajit:bootstrap3-datepicker@1.3.1
rajit:bootstrap3-datepicker@1.4.1
random@1.0.2
reactive-dict@1.0.5
reactive-var@1.0.4
reload@1.1.2
retry@1.0.2
routepolicy@1.0.4
sacha:juice@0.1.1
sacha:juice@0.1.2
sacha:spin@2.0.4
service-configuration@1.0.3
session@1.0.5
@ -110,7 +110,7 @@ spiderable@1.0.6
srp@1.0.2
standard-app-packages@1.0.4
stylus@1.0.6
tap:i18n@1.3.2
tap:i18n@1.4.1
telescope-api@0.0.0
telescope-base@0.0.0
telescope-daily@0.0.0
@ -130,6 +130,8 @@ telescope-releases@0.1.0
telescope-rss@0.0.0
telescope-search@0.0.0
telescope-singleday@0.1.0
telescope-subscribe-to-posts@0.1.0
telescope-tagline-banner@0.1.0
telescope-tags@0.0.0
telescope-theme-base@0.0.0
telescope-theme-hubble@0.0.0
@ -141,7 +143,7 @@ twitter@1.1.3
ui@1.0.5
underscore@1.0.2
url@1.0.3
useraccounts:core@1.6.1
useraccounts:unstyled@1.6.1
useraccounts:core@1.8.1
useraccounts:unstyled@1.8.1
webapp@1.1.6
webapp-hashing@1.0.2

14
.travis.yml Normal file
View file

@ -0,0 +1,14 @@
language: node_js
node_js:
- "0.10"
before_install:
- curl https://install.meteor.com | /bin/sh
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
# Add testing package since it's not currently enabled in Telescope
- printf "sanjo:jasmine@0.11.0" >> .meteor/packages
script:
- JASMINE_BROWSER=Firefox meteor --test

View file

@ -1,8 +1,77 @@
## v0.14.3 “TableScope”
* Implemented Reactive Table for the Users dashboard (thanks @jshimko!).
* Upgraded Herald package (thanks @kestanous!).
* Upgraded Avatar package (thanks @bengott!).
* Upgraded Autoform package.
* Added Greek translation (thanks @portokallidis!).
* Improved Spanish translation (thanks @brayancruces!).
* Added new callbacks for upvoting and downvoting (thanks @Baxter900 !).
## v0.14.2 “FaviconScope”
* Added settings for auth methods.
* Added setting for external fonts.
* Use site tagline as homepage title.
* Make favicon customizable.
* Making webfont customizable. To get previous font back, use: `https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic`.
* Fix juice issue.
* Non-admins should not be able to access rejected posts.
* Bulgarian translation (thanks @durrrr91!)
## v0.14.1 “TaglineScope”
* Fix double notification bug.
* Fix singleday view bug.
* Fix post approval date bug.
* Don't let non-admins access pending posts.
* Give search field a border on white backgrounds.
* Spanish, Brazilian, Turkish, Vietnamese, Polish translations (thanks everybody!).
* Do not put comment reply page behind log-in wall.
* Various CSS tweaks.
* Added tagline banner package.
* You can now assign a category to posts generated from feeds (thanks @Accentax!).
* Use tagline as title on homepage.
* Refactor default view route controller code.
* Fixed security issue with post editing.
## v0.14.0 “GridScope”
* Added Grid Layout option.
* Cleaned up vote click handling functions and added tests (thanks @anthonymayer!).
* Added `threadModules` zone.
* Added `upvoteCallbacks` and `downvoteCallbacks` callback arrays.
* Fix “post awaiting moderation” message bug.
* You can now subscribe to comment threads (thanks @delgermurun!).
* Added `postApproveCallbacks` callback array.
* Added notifications for pending and approved posts, for admins and end users.
* Renaming "digest" view to "singleday".
* Make sure only valid properties can be added to posts and comments.
* Added newsletter time setting (thanks @anthonymayer!).
* Change "sign up" to "register" (thanks @Kikobeats!).
## v0.13.0 “ComponentScope”
* Tweaked comments layout in Hubbble theme.
* Added Bulgarian translation (thanks @toome123!).
* Cleaned up permission functions (thanks @anthonymayer!).
* Various fixes (thanks @comerc and @Kikobeats!).
* Stopped synced-cron message logging.
* Limit all posts lists to 200 posts.
* Refactored posts lists to use the template-level subscription pattern when appropriate.
* Refactored `single day` and `daily` packages.
* Footer field now accepts Markdown instead of HTML.
* Feeds can now be assigned to a user.
* Various CSS tweaks.
* Fixing newsletter issue.
* Post rank now has its own module.
* Changed how field label i18n works.
## v0.12.0 “DummyScope”
**Important: existing newsletters and feeds need to be manually enabled in the Settings panel**
**Important: existing newsletters and feeds need to be manually enabled in the Settings panel**
* Added "Enable Newsletter" setting. Note: existing newsletters must be re-enabled.
* Added "Enable Newsletter" setting. Note: existing newsletters must be re-enabled.
* Added "Enable Feeds" settings. Note: existing feeds must be re-enabled.
* Now showing release notes for latest version right inside the app.
* Added dummy posts, users, and comments.
@ -22,21 +91,21 @@
* `telescope-post-by-feed` package now lets you import posts from RSS feeds.
* Adding limit of 200 posts to post list request.
* Refactoring post and comment submit to fix latency compensation issues.
* Tags package now using Autoform.
* Tags package now using Autoform.
## v0.11.0 “AvatarScope”
* Added new `userCreatedCallbacks` callback hook.
* Added new setting to subscribe new user to mailing list automatically.
* Added new `debug` setting.
* Added new `debug` setting.
* `siteUrl` setting now affects `Meteor.absoluteUrl()`.
* Added new `clog` function that only logs if `debug` setting is true.
* Simplified post module system, modules are not split in three zones anymore.
* Added new `postThumbnail` hook to show Embedly thumbnail.
* Added new `postThumbnail` hook to show Embedly thumbnail.
* Simplified Hubble theme CSS for both desktop and mobile.
* Many CSS tweaks for Hubble mobile.
* Many CSS tweaks for Hubble mobile.
* Show author and commenters avatars on post item.
* Adding description to post list pages and showing them in menus.
* Adding description to post list pages and showing them in menus.
* Improved Russian translation (thanks @Viktorminator!).
* Now using `editorconfig` (thanks @erasaur!).
* Upgraded to `useraccounts:unstyled@1.4.0` (thanks @splendido!).
@ -54,10 +123,10 @@
## v0.9.11 “FormScope”
* Now using [Autoform](https://github.com/aldeed/meteor-autoform/)'s **quickform** feature to generate post submit and edit forms.
* Now using [Autoform](https://github.com/aldeed/meteor-autoform/)'s **quickform** feature to generate post submit and edit forms.
* Various fixes by [@anthonymayer](https://github.com/anthonymayer).
* Now using [fourseven:scss](https://github.com/fourseven/meteor-scss) to directly compile SCSS files.
* Renamed `post` method to `submitPost`.
* Now using [fourseven:scss](https://github.com/fourseven/meteor-scss) to directly compile SCSS files.
* Renamed `post` method to `submitPost`.
* Post editing now happens via a `postEdit` method.
* Categories are now normalized (only the `_id` is stored on the post object, not the whole category object).
* Refactored Embedly package; now fills in description as well (thanks [@kvindasAB](https://github.com/kvindasAB)!).
@ -101,12 +170,12 @@
* Splitting up the settings form into sub-sections.
* Adding help text to settings form.
* Fixing problem with daily view theming.
* Improving avatar stuff (thanks @shaialon and @bengott!).
* Improving avatar stuff (thanks @shaialon and @bengott!).
## v0.9.6
* Fixed security hole in user update.
* Kadira is now included by default.
* Fixed security hole in user update.
* Kadira is now included by default.
* Comments now have their own feed (thanks @delgermurun!).
* Fixed URL collision bug (thanks @GoodEveningMiss!).
* Now using [`account-templates`](https://github.com/splendido/accounts-templates-core) (thanks @splendido!).
@ -124,7 +193,7 @@
## v0.9.4 “UpdateScope”
* Removed unneeded allow insert on Posts and Comments.
* Removed unneeded allow insert on Posts and Comments.
* Renaming `postMeta` template to `postInfo` to avoid ambiguity.
* Fixing avatar code.
* Adding update prompt package.
@ -134,24 +203,24 @@
## v0.9.3 “DailyScope”
* Show user comments on user profile page.
* Show user comments on user profile page.
* Move votes to their own `user.votes` object.
* Add daily view.
* Default root view is now customizable.
* Default root view is now customizable.
* Updated app to 0.9.0.
* Updated all packages to be 0.9.0-compatible.
* Fixed XSS bug (CVE ID: CVE-2014-5144) by sanitizing user input server-side.
* Now storing both markdown and HTML versions of content.
* Now storing both markdown and HTML versions of content.
## v0.9.2.6 “InviteScope”
* Added new invite features (thanks [@callmephilip](https://github.com/callmephilip)!)
* Changed `navItems` to `primaryNav` and added `secondaryNav`.
* Changed `navItems` to `primaryNav` and added `secondaryNav`.
* Added new `themeSettings` object for storing theme-level settings.
* Notifications is now a nav menu item.
* Notifications is now a nav menu item.
* Renamed `comments` to `commentsCount` on `Post` model.
* Now tracking list of commenters `_id`s on `Post` model.
* Rerun interrupted migrations.
* Rerun interrupted migrations.
## v0.9.2.5 “AccountScope”
@ -161,19 +230,19 @@
## v0.9.2 “MailScope”
* Use [handlebars-server](https://github.com/EventedMind/meteor-handlebars-server) for all email templates.
* Use [handlebars-server](https://github.com/EventedMind/meteor-handlebars-server) for all email templates.
* Refactored email system to use global HTML email wrapper.
* Added routes to preview email templates.
* Added routes to preview email templates.
* Changed how notifications are stored in db.
* Added `deleteNotifications` migration to delete all existing notifications.
* Now using templates for on-site notifications too.
* Added `heroModules` and `footerModules` hooks.
* Added [telescope-newsletter](https://github.com/TelescopeJS/Telescope-Newsletter) package.
* Sending emails from within `setTimeout`s to fix latency compensation issue.
* Added [telescope-newsletter](https://github.com/TelescopeJS/Telescope-Newsletter) package.
* Sending emails from within `setTimeout`s to fix latency compensation issue.
## v0.9.1.2
* Added `lastCommentedAt` property to posts.
* Added `lastCommentedAt` property to posts.
* Added hooks to `post_edit` and `post_submit`'s `rendered` callback.
* Embedly module now supports video embedding in a lightbox.
* Updated to Meteor 0.8.3.
@ -183,16 +252,16 @@
* Using Arunoda's [Subscription Manager](https://github.com/meteorhacks/subs-manager).
* Updating mobile version.
* Made the background color setting into a more general background CSS setting.
* Added `postHeading` and `postMeta` hooks.
* Made the background color setting into a more general background CSS setting.
* Added `postHeading` and `postMeta` hooks.
## v0.9
* See [blog post](http://telesc.pe/blog/telescope-v09-modulescope) for changelog.
* See [blog post](http://telesc.pe/blog/telescope-v09-modulescope) for changelog.
## v0.8.3 “CleanScope”
* Refactored the way dating and timestamping works with pending/approved posts.
* Refactored the way dating and timestamping works with pending/approved posts.
* Cleaned up unused/old third-party code.
* Migrated "submitted" property to "postedAt".
* Added a "postedAt" property to comments.
@ -202,24 +271,24 @@
* Improved migrations with timestamps and number of rows affected.
* Created `telescope-lib` and `telescope-base` pacakge.
* Pulled out search into its own `telescope-search` package.
* Made menu and views modular.
* Made menu and views modular.
* Using SimpleSchema and Collection2 for models.
## v0.8.1 “FlexScope”
* Extracted part of the tags feature into its own package.
* Extracted part of the tags feature into its own package.
* Made subscription preloader more flexible.
* Made navigation menu dynamic.
* Made navigation menu dynamic.
## v0.8 “BlazeScope”
* Updated for Meteor 0.8.1.1/Blaze compatibility.
* Using Collection2/SimpleSchema/Autoforms for Settings form.
* Using Collection2/SimpleSchema/Autoforms for Settings form.
## v0.7.4 “InterScope”
* Added basic internationalization (thanks Toam!).
* Added search logging.
* Added search logging.
## v0.7.3
@ -234,9 +303,9 @@
* Added karma redistribution.
* Improved user dashboard.
* Improved user profiles.
* Improved user profiles.
Note: run the "update user profile" script from the toolbox after updating.
Note: run the "update user profile" script from the toolbox after updating.
## v0.7 “IronScope”
@ -252,7 +321,7 @@ Note: run the "update user profile" script from the toolbox after updating.
* Paginating users dashboard.
* Filtering users dashboard.
Note: If you're upgrading from a previous version of Telescope, you'll need to run the "update user slugs" method from within the Admin Toolbox panel inside the web app to get user profiles to work.
Note: If you're upgrading from a previous version of Telescope, you'll need to run the "update user slugs" method from within the Admin Toolbox panel inside the web app to get user profiles to work.
## v0.6.2
@ -299,4 +368,4 @@ Note: If you're upgrading from a previous version of Telescope, you'll need to r
* Added a second `createdAt` timestamp. Score calculations still use the `submitted` timestamp, but it only gets set when (if) a post gets approved.
* Started keeping track of versions and changes.
* Started keeping track of versions and changes.

View file

@ -1,3 +1,6 @@
[![Build Status](https://travis-ci.org/TelescopeJS/Telescope.svg)](https://travis-ci.org/TelescopeJS/Telescope)
[![Code Climate](https://codeclimate.com/github/TelescopeJS/Telescope/badges/gpa.svg)](https://codeclimate.com/github/TelescopeJS/Telescope)
Telescope is an open-source, real-time social news site built with [Meteor](http://meteor.com)
**Note:** Telescope is beta software. Most of it should work but it's still a little unpolished and you'll probably find some bugs. Use at your own risk :)
@ -5,9 +8,8 @@ Telescope is an open-source, real-time social news site built with [Meteor](http
Note that Telescope is distributed under the [MIT License](http://opensource.org/licenses/MIT)
### We Need Your Help!
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/TelescopeJS/Telescope?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
A lot of work has already gone into Telescope, but it needs that final push to reach its full potential.
A lot of work has already gone into Telescope, but it needs that final push to reach its full potential.
So if you'd like to be part of the project, please check out the [roadmap](https://trello.com/b/oLMMqjVL/telescope-roadmap) and [issues](https://github.com/TelescopeJS/Telescope/issues) to see if there's anything you can help with.

View file

@ -15,8 +15,7 @@ UI.registerHelper('eachWithRank', function(items, options) {
});
UI.registerHelper('getSetting', function(setting, defaultArgument){
var defaultArgument = (typeof defaultArgument !== 'undefined') ? defaultArgument : '';
var setting = getSetting(setting, defaultArgument);
setting = getSetting(setting, defaultArgument);
return setting;
});
UI.registerHelper('isLoggedIn', function() {
@ -32,13 +31,13 @@ UI.registerHelper('canComment', function() {
return can.comment(Meteor.user());
});
UI.registerHelper('isAdmin', function(showError) {
if(isAdmin(Meteor.user())){
if (isAdmin(Meteor.user())) {
return true;
}else{
if((typeof showError === "string") && (showError === "true"))
flashMessage(i18n.t('sorry_you_do_not_have_access_to_this_page'), "error");
return false;
}
if ((typeof showError === 'string') && (showError === 'true')) {
flashMessage(i18n.t('sorry_you_do_not_have_access_to_this_page'), 'error');
}
return false;
});
UI.registerHelper('canEdit', function(item) {
return can.edit(Meteor.user(), item, false);
@ -48,19 +47,19 @@ UI.registerHelper('log', function(context){
console.log(context);
});
UI.registerHelper("formatDate", function(datetime, format) {
UI.registerHelper('formatDate', function(datetime, format) {
Session.get('momentLocale'); // depend on session variable to reactively rerun the helper
return moment(datetime).format(format);
});
UI.registerHelper("timeAgo", function(datetime) {
UI.registerHelper('timeAgo', function(datetime) {
Session.get('momentLocale'); // depend on session variable to reactively rerun the helper
return moment(datetime).fromNow()
return moment(datetime).fromNow();
});
UI.registerHelper("sanitize", function(content) {
console.log('cleaning up…')
console.log(content)
UI.registerHelper('sanitize', function(content) {
console.log('cleaning up…');
console.log(content);
return cleanUp(content);
});
@ -69,14 +68,23 @@ UI.registerHelper('pluralize', function(count, string) {
return i18n.t(string);
});
UI.registerHelper("profileUrl", function(userOrUserId) {
var user = (typeof userOrUserId === "string") ? Meteor.users.findOne(userOrUserId) : userOrUserId;
if (!!user)
UI.registerHelper('profileUrl', function(userOrUserId) {
var user = (typeof userOrUserId === 'string') ? Meteor.users.findOne(userOrUserId) : userOrUserId;
if (!!user) {
return getProfileUrl(user);
}
});
UI.registerHelper("userName", function(userOrUserId) {
var user = (typeof userOrUserId === "string") ? Meteor.users.findOne(userOrUserId) : userOrUserId;
if (!!user)
UI.registerHelper('userName', function(userOrUserId) {
var user = (typeof userOrUserId === 'string') ? Meteor.users.findOne(userOrUserId) : userOrUserId;
if (!!user) {
return getUserName(user);
}
});
UI.registerHelper('displayName', function(userOrUserId) {
var user = (typeof userOrUserId === 'string') ? Meteor.users.findOne(userOrUserId) : userOrUserId;
if (!!user) {
return getDisplayName(user);
}
});

View file

@ -1,6 +1,5 @@
<head>
<title>Loading...</title>
<meta name="viewport" content="initial-scale=1.0">
<link rel="shortcut icon" href="/img/favicon.ico"/>
<link id="rss-link" rel="alternate" type="application/rss+xml" title="New Posts" href="/feed.xml"/>
</head>

View file

@ -2,36 +2,36 @@ AutoForm.hooks({
updateSettingsForm: {
before: {
update: function(docId, modifier, template) {
template.$('button[type=submit]').addClass('loading');
update: function(modifier) {
this.template.$('button[type=submit]').addClass('loading');
return modifier;
}
},
onSuccess: function(operation, result, template) {
template.$('button[type=submit]').removeClass('loading');
onSuccess: function(operation, result) {
this.template.$('button[type=submit]').removeClass('loading');
},
onError: function(operation, result, template) {
template.$('button[type=submit]').removeClass('loading');
this.template.$('button[type=submit]').removeClass('loading');
}
},
insertSettingsForm: {
before: {
insert: function(doc, template) {
template.$('button[type=submit]').addClass('loading');
insert: function(doc) {
this.template.$('button[type=submit]').addClass('loading');
return doc;
}
},
onSuccess: function(operation, result, template) {
template.$('button[type=submit]').removeClass('loading');
onSuccess: function(operation, result) {
this.template.$('button[type=submit]').removeClass('loading');
},
onError: function(operation, result, template) {
template.$('button[type=submit]').removeClass('loading');
onError: function(operation, result) {
this.template.$('button[type=submit]').removeClass('loading');
}
}

View file

@ -1,5 +0,0 @@
<template name="comment_deleted">
<div class="grid-small grid-block">
<p>{{_ "your_comment_has_been_deleted"}}</p>
</div>
</template>

View file

@ -11,7 +11,6 @@ Template[getTemplate('comment_form')].events({
$(e.target).addClass('disabled');
clearSeenMessages();
var comment = {};
var $commentForm = instance.$('#comment');
var $submitButton = instance.$('.btn-submit');
@ -23,7 +22,9 @@ Template[getTemplate('comment_form')].events({
$commentForm.val('');
var post = postObject;
// context can be either post, or comment property
var postId = !!this._id ? this._id: this.comment.postId;
var post = Posts.findOne(postId);
comment = {
postId: post._id,

View file

@ -15,20 +15,20 @@
<span>{{_ "downvote"}}</span>
</a>
</div>
<div class="user-avatar">{{> avatar userId=userId shape="circle"}}</div>
<div class="comment-main">
<div class="comment-meta">
<a class="comment-username" href="{{profileUrl}}">{{authorName}}</a>
<span class="comment-time">{{timeAgo ago}},</span>
<span class="points">{{upvotes}}</span> <span class="unit">points </span>
<a href="{{pathFor route='comment_reply' _id=_id}}" class="comment-permalink icon-link goto-comment">{{_ "link"}}</a>
{{#if canEdit this}}
| <a class="edit-link" href="{{pathFor route='comment_edit' _id=_id}}">{{_ "edit"}}</a>
{{/if}}
{{#if isAdmin}}
| <span>{{full_date}}</span>
{{/if}}
</div>
<div class="comment-meta">
<div class="user-avatar avatar-medium">{{> avatar userId=userId shape="circle"}}</div>
<a class="comment-username" href="{{profileUrl}}">{{authorName}}</a>
<span class="comment-time">{{timeAgo ago}},</span>
<span class="points">{{upvotes}}</span> <span class="unit">points </span>
<a href="{{pathFor route='comment_reply' _id=_id}}" class="comment-permalink icon-link goto-comment">{{_ "link"}}</a>
{{#if canEdit this}}
| <a class="edit-link" href="{{pathFor route='comment_edit' _id=_id}}">{{_ "edit"}}</a>
{{/if}}
{{#if isAdmin}}
| <span>{{full_date}}</span>
{{/if}}
</div>
<div class="comment-main">
<div class="comment-text markdown">{{{htmlBody}}}</div>
<a href="{{pathFor route='comment_reply' _id=_id}}" class="comment-reply goto-comment">{{_ "reply"}}</a>
</div>
@ -36,7 +36,7 @@
{{/if}}
{{#if showChildComments}}
<ul class="comment-children comment-list">
{{#each child_comments}}
{{#each childComments}}
{{#with this}}
{{> UI.dynamic template=comment_item}}
{{/with}}

View file

@ -1,45 +1,45 @@
findQueueContainer=function($comment){
findQueueContainer = function($comment) {
// go up and down the DOM until we find either A) a queue container or B) an unqueued comment
$up=$comment.prevAll(".queue-container, .comment-displayed").first();
$down=$comment.nextAll(".queue-container, .comment-displayed").first();
$prev=$comment.prev();
$next=$comment.next();
$queuedAncestors=$comment.parents(".comment-queued");
if($queuedAncestors.exists()){
$up = $comment.prevAll(".queue-container, .comment-displayed").first();
$down = $comment.nextAll(".queue-container, .comment-displayed").first();
$prev = $comment.prev();
$next = $comment.next();
$queuedAncestors = $comment.parents(".comment-queued");
if ($queuedAncestors.exists()) {
// console.log("----------- case 1: Queued Ancestor -----------");
// 1.
// our comment has one or more queued ancestor, so we look for the root-most
// ancestor's queue container
$container=$queuedAncestors.last().data("queue");
}else if($prev.hasClass("queue-container")){
$container = $queuedAncestors.last().data("queue");
} else if ($prev.hasClass("queue-container")) {
// console.log("----------- case 2: Queued Brother -----------");
// 2.
// the comment just above is queued, so we use the same queue container as him
$container=$prev.data("queue");
}else if($prev.find(".comment").last().hasClass("comment-queued")){
$container = $prev.data("queue");
} else if ($prev.find(".comment").last().hasClass("comment-queued")) {
// console.log("----------- case 3: Queued Cousin -----------");
// 3.
// there are no queued comments going up on the same level,
// but the bottom-most child of the comment directly above is queued
$container=$prev.find(".comment").last().data("queue");
}else if($down.hasClass("queue-container")){
$container = $prev.find(".comment").last().data("queue");
} else if ($down.hasClass("queue-container")) {
// console.log("----------- case 4: Queued Sister -----------");
// 3.
// the comment just below is queued, so we use the same queue container as him
$container=$next.data("queue");
}else if($up.hasClass('comment-displayed') || !$up.exists()){
$container = $next.data("queue");
} else if ($up.hasClass('comment-displayed') || !$up.exists()) {
// console.log("----------- case 5: No Queue -----------");
// 4.
// we've found containers neither above or below, but
// A) we've hit a displayed comment or
// B) we've haven't found any comments (i.e. we're at the beginning of the list)
// so we put our queue container just before the comment
$container=$('<div class="queue-container"><ul></ul></div>').insertBefore($comment);
$container = $('<div class="queue-container"><ul></ul></div>').insertBefore($comment);
$container.click(function(e){
e.preventDefault();
var links=$(this).find("a");
var links = $(this).find("a");
links.each(function(){
var target=$(this).attr("href");
var target = $(this).attr("href");
$(target).removeClass("comment-queued").addClass("comment-displayed");
// add comment ID to global array to avoid queuing it again
window.openedComments.push(target.substr(1));
@ -69,9 +69,9 @@ Template[getTemplate('comment_item')].helpers({
full_date: function(){
return this.createdAt.toString();
},
child_comments: function(){
childComments: function(){
// return only child comments
return Comments.find({parentCommentId: this._id });
return Comments.find({parentCommentId: this._id});
},
author: function(){
return Meteor.users.findOne(this.userId);
@ -95,92 +95,31 @@ Template[getTemplate('comment_item')].helpers({
},
profileUrl: function(){
var user = Meteor.users.findOne(this.userId);
if(user)
if (user) {
return getProfileUrl(user);
}
}
});
Template[getTemplate('comment_item')].rendered=function(){
// if(this.data){
// var comment=this.data;
// var $comment=$("#"+comment._id);
// if(Meteor.user() && Meteor.user()._id==comment.userId){
// // if user is logged in, and the comment belongs to the user, then never queue it
// }else if(this.isQueued && !$comment.hasClass("comment-queued") && window.openedComments.indexOf(comment._id)==-1){
// // if comment is new and has not already been previously queued
// // note: testing on the class works because Meteor apparently preserves newly assigned CSS classes
// // across template renderings
// // TODO: save scroll position
// // get comment author name
// var user=Meteor.users.findOne(comment.userId);
// var author=getDisplayName(user);
// var imgURL=getAvatarUrl(user);
// var $container=findQueueContainer($comment);
// var comment_link='<li class="icon-user"><a href="#'+comment._id+'" class="has-tooltip" style="background-image:url('+imgURL+')"><span class="tooltip"><span>'+author+'</span></span></a></li>';
// $(comment_link).appendTo($container.find("ul"));
// // $(comment_link).appendTo($container.find("ul")).hide().fadeIn("slow");
// $comment.removeClass("comment-displayed").addClass("comment-queued");
// $comment.data("queue", $container);
// // TODO: take the user back to their previous scroll position
// }
// }
var handleVoteClick = function (meteorMethodName, eventName, e, instance) {
e.preventDefault();
if (!Meteor.user()){
Router.go('atSignIn');
flashMessage(i18n.t('please_log_in_first'), 'info');
} else {
Meteor.call(meteorMethodName, this, function(error, result){
trackEvent(eventName, {
'commentId': instance.data._id,
'postId': instance.data.post,
'authorId': instance.data.userId
});
});
}
};
Template[getTemplate('comment_item')].events({
'click .queue-comment': function(e){
e.preventDefault();
var current_comment_id=$(event.target).closest(".comment").attr("id");
var now = new Date();
var comment_id = Comments.update(current_comment_id,
{
$set: {
postedAt: new Date().getTime()
}
}
);
},
'click .not-upvoted .upvote': function(e, instance){
e.preventDefault();
if(!Meteor.user()){
Router.go('atSignIn');
flashMessage(i18n.t("please_log_in_first"), "info");
}
Meteor.call('upvoteComment', this, function(error, result){
trackEvent("post upvoted", {'commentId':instance.data._id, 'postId': instance.data.post, 'authorId':instance.data.userId});
});
},
'click .upvoted .upvote': function(e, instance){
e.preventDefault();
if(!Meteor.user()){
Router.go('atSignIn');
flashMessage(i18n.t("please_log_in_first"), "info");
}
Meteor.call('cancelUpvoteComment', this, function(error, result){
trackEvent("post upvote cancelled", {'commentId':instance.data._id, 'postId': instance.data.post, 'authorId':instance.data.userId});
});
},
'click .not-downvoted .downvote': function(e, instance){
e.preventDefault();
if(!Meteor.user()){
Router.go('atSignIn');
flashMessage(i18n.t("please_log_in_first"), "info");
}
Meteor.call('downvoteComment', this, function(error, result){
trackEvent("post downvoted", {'commentId':instance.data._id, 'postId': instance.data.post, 'authorId':instance.data.userId});
});
},
'click .downvoted .downvote': function(e, instance){
e.preventDefault();
if(!Meteor.user()){
Router.go('atSignIn');
flashMessage(i18n.t("please_log_in_first"), "info");
}
Meteor.call('cancelDownvoteComment', this, function(error, result){
trackEvent("post downvote cancelled", {'commentId':instance.data._id, 'postId': instance.data.post, 'authorId':instance.data.userId});
});
}
'click .not-upvoted .upvote': _.partial(handleVoteClick, 'upvoteComment', 'post upvoted'),
'click .upvoted .upvote': _.partial(handleVoteClick, 'cancelUpvoteComment', 'post upvote cancelled'),
'click .not-downvoted .downvote': _.partial(handleVoteClick, 'downvoteComment', 'post downvoted'),
'click .downvoted .downvote': _.partial(handleVoteClick, 'cancelDownvoteComment', 'post downvote cancelled')
});

View file

@ -4,4 +4,7 @@
{{> UI.dynamic template=comment_item}}
{{/each}}
</ul>
{{#each threadModules}}
{{> UI.dynamic template=getTemplate data=..}}
{{/each}}
</template>

View file

@ -1,7 +1,3 @@
Template[getTemplate('comment_list')].created = function(){
postObject = this.data;
};
Template[getTemplate('comment_list')].helpers({
comment_item: function () {
return getTemplate('comment_item');
@ -10,6 +6,12 @@ Template[getTemplate('comment_list')].helpers({
var post = this;
var comments = Comments.find({postId: post._id, parentCommentId: null}, {sort: {score: -1, postedAt: -1}});
return comments;
},
threadModules: function () {
return threadModules;
},
getTemplate: function () {
return getTemplate(this.template);
}
});

View file

@ -2,7 +2,7 @@
<div class="grid comment-page single-post">
{{#with post}}
<div class="posts">
<div class="posts posts-list">
{{> UI.dynamic template=post_item}}
</div>
{{/with}}

View file

@ -1,11 +1,23 @@
<template name="css">
<style>
@import url({{getSetting 'fontUrl'}});
body, textarea, input, button, input[type="submit"], input[type="button"]{
font-family: {{getSetting 'fontFamily'}};
}
body{
background: {{getSetting "backgroundCSS"}};
}
input[type="submit"], button, .button, .auth-buttons #login-buttons #login-buttons-password, .btn-primary, .error, .mobile-menu-button, .login-link-text, .post-category:hover{
background-color: {{getSetting "buttonColor"}} !important;
color: {{getSetting "buttonTextColor"}} !important;
.header{
background-color: {{getSetting "headerColor"}};
}
.header, .header .logo a, .header .logo a:visited{
color: {{getSetting "headerTextColor"}};
}
input[type="submit"], button, .button, button.submit, .auth-buttons #login-buttons #login-buttons-password, .btn-primary, .header .btn-primary, .header .btn-primary:link, .header .btn-primary:visited, .error, .mobile-menu-button, .login-link-text, .post-category:hover{
background-color: {{getSetting "buttonColor"}};
color: {{getSetting "buttonTextColor"}};
}
a:hover, .post-content .post-heading .post-title:hover, .post-content .post-upvote .upvote-link i, .comment-actions a i, .comment-actions.upvoted .upvote i, .comment-actions.downvoted .downvote i, .toggle-actions-link, .post-meta a:hover, .action:hover, .post-upvote .upvote-link i{
color: {{getSetting "buttonColor"}};
@ -18,16 +30,14 @@
}
.post-content .post-upvote .upvote-link.voted i.icon-check{
/*color: {{getSetting "secondaryColor"}};*/
}
.logo-image a{
max-height:{{getSetting "logoHeight"}}px;
max-width:{{getSetting "logoWidth"}}px;
}
.header{
background-color: {{getSetting "headerColor"}};
}
.header, .header .logo a, .header .logo a:visited{
color: {{getSetting "headerTextColor"}};
}
.logo-image a{
height:{{getSetting "logoHeight"}}px;
width:{{getSetting "logoWidth"}}px;
}
{{hideAuthClass}}
</style>
</template>

View file

@ -1,2 +1,27 @@
Template[getTemplate('css')].helpers({
hideAuthClass: function () {
var authClass = '';
var authMethods = getSetting('authMethods', ["email"]);
var selectors = [
{name: 'email', selector: ".at-pwd-form"},
{name: 'twitter', selector: "#at-twitter"},
{name: 'facebook', selector: "#at-facebook"}
];
selectors.forEach(function (method) {
// if current method is not one of the enabled auth methods, hide it
if (authMethods.indexOf(method.name) == -1) {
authClass += method.selector + ", ";
}
});
// unless we're showing at least one of twitter and facebook AND the password form,
// hide separator
if (authMethods.indexOf('email') == -1 || (authMethods.indexOf('facebook') == -1 && authMethods.indexOf('twitter') == -1)) {
authClass += ".at-sep, ";
}
return authClass.slice(0, - 2) + "{display:none !important}";
}
});

View file

@ -1,7 +1,7 @@
<template name="footer">
{{#if footerCode}}
<div class="footer grid {{footerClass}}">
{{{footerCode}}}
{{#markdown}}{{footerCode}}{{/markdown}}
</div>
{{/if}}
{{#each footerModules}}

View file

@ -20,6 +20,9 @@ Template[getTemplate('layout')].helpers({
css: function () {
return getTemplate('css');
},
extraCode: function() {
return getSetting('extraCode');
},
heroModules: function () {
return heroModules;
},
@ -37,4 +40,21 @@ Template[getTemplate('layout')].rendered = function(){
$('body').scrollTop(currentScroll);
Session.set('currentScroll', null);
}
// favicon
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = getSetting('faviconUrl', '/img/favicon.ico');
document.getElementsByTagName('head')[0].appendChild(link);
};
Template[getTemplate('layout')].events({
'click .inner-wrapper': function (e) {
if ($('body').hasClass('mobile-nav-open')) {
e.preventDefault();
$('body').removeClass('mobile-nav-open');
}
}
});

View file

@ -36,7 +36,7 @@
{{#if showField}}
<div class="form-group {{#if afFieldIsInvalid name=this.atts.name}}has-error{{/if}}">
<label class="control-label">
{{_ this.atts.name}}
{{_ label}}
{{#if fieldIsPrivate}}
<span class="private-field" title="{{_ 'Private'}}">(p)</span>
{{/if}}

View file

@ -6,27 +6,20 @@ var findAtts = function () {
return c && c.atts;
}
var getSchema = function () {
var schema = AutoForm.find().ss._schema;
// decorate schema with key names
schema = _.map(schema, function (field, key) {
field.name = key;
return field;
});
return schema;
}
var canEditField = function (field) {
// show field only if user is admin or it's marked as editable
return isAdmin(Meteor.user()) || !!field.atts.editable || (!!field.afFieldInputAtts && !!field.afFieldInputAtts.editable)
return isAdmin(Meteor.user()) || (!!field.atts && !!field.atts.editable) || (!!field.afFieldInputAtts && !!field.afFieldInputAtts.editable)
}
Template[getTemplate('quickForm_telescope')].helpers({
fieldsWithNoFieldset: function () {
// get names of fields who don't have an autoform attribute or don't have a group, but are not omitted
var fields = _.pluck(_.filter(getSchema(), function (field, key) {
// note: we need to _.map() first to assign the field key to the "name" property to preserve it.
var fields = _.pluck(_.filter(_.map(AutoForm.getFormSchema()._schema, function (field, key) {
field.name = key;
return field;
}), function (field) {
if (field.name.indexOf('$') !== -1) // filter out fields with "$" in their name
return false
if (field.autoform && field.autoform.omit) // filter out fields with omit = true
@ -34,11 +27,12 @@ Template[getTemplate('quickForm_telescope')].helpers({
if (field.autoform && field.autoform.group) // filter out fields with a group
return false
return true // return remaining fields
}), 'name');
}), "name");
return fields;
},
afFieldsets: function () {
var groups = _.compact(_.uniq(_.pluckDeep(getSchema(), 'autoform.group')));
var groups = _.compact(_.uniq(_.pluckDeep(AutoForm.getFormSchema()._schema, 'autoform.group')));
// if user is not admin, exclude "admin" group from fieldsets
if (!isAdmin(Meteor.user()))
@ -53,7 +47,7 @@ Template[getTemplate('quickForm_telescope')].helpers({
var fieldset = this.toLowerCase();
// get names of fields whose group match the current fieldset
var fields = _.pluck(_.filter(getSchema(), function (field, key) {
var fields = _.pluck(_.filter(AutoForm.getFormSchema()._schema, function (field, key) {
return (field.name.indexOf('$') === -1) && field.autoform && field.autoform.group == fieldset;
}), 'name');
@ -133,6 +127,15 @@ Template["afFormGroup_telescope"].helpers({
},
afFieldInstructions: function () {
return this.afFieldInputAtts.instructions;
},
label: function () {
var fieldName = this.name;
var fieldSchema = AutoForm.getFormSchema().schema(fieldName);
// if a label has been explicitely specified, use it; else default to i18n of the field name
var label = !!fieldSchema.label ? fieldSchema.label: i18n.t(fieldName);
return label;
}
});

View file

@ -6,7 +6,7 @@ Template[getTemplate('mobile_nav')].helpers({
return secondaryNav;
},
getTemplate: function () {
return getTemplate(this).template;
return getTemplate(this.template);
}
});

View file

@ -1,5 +1,5 @@
<template name="nav">
<header class="header">
<header class="header {{headerClass}}">
<a href="#menu" class="mobile-only mobile-menu-button button">
<svg height="24px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path class="hamburger" d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z"/></svg>

View file

@ -22,12 +22,17 @@ Template[getTemplate('nav')].helpers({
},
logo_url: function(){
return getSetting('logoUrl');
},
headerClass: function () {
var color = getSetting('headerColor');
return (color == 'white' || color == '#fff' || color == '#ffffff') ? "white-background" : '';
}
});
Template[getTemplate('nav')].events({
'click .mobile-menu-button': function(e){
e.preventDefault();
e.stopPropagation(); // Make sure we don't immediately close the mobile nav again. See layout.js event handler.
$('body').toggleClass('mobile-nav-open');
}
});

View file

@ -11,7 +11,7 @@
</div>
</div>
{{else}}
<a class="account-link sign-in" href="{{pathFor route='atSignIn'}}">{{_ "sign_in"}}</a>
<a class="account-link sign-up" href="{{pathFor route='atSignUp'}}">{{_ "sign_up"}}</a>
<a class="account-link sign-in" href="{{pathFor route='atSignUp'}}">{{_ "sign_up"}}</a>
<a class="account-link sign-up" href="{{pathFor route='atSignIn'}}">{{_ "sign_in"}}</a>
{{/if}}
</template>
</template>

View file

@ -6,9 +6,9 @@ Template[getTemplate('userMenu')].helpers({
return getDisplayName(Meteor.user());
},
profileUrl: function () {
return Router.routes['user_profile'].path({_idOrSlug: Meteor.user().slug});
return Router.path('user_profile', {_idOrSlug: Meteor.user().slug});
},
userEditUrl: function () {
return Router.routes['user_edit'].path(Meteor.user());
return Router.path('user_edit', {slug: Meteor.user().slug});
}
});

View file

@ -1,13 +1,11 @@
<template name="postAdmin">
{{#if isAdmin}}
<div class="post-meta-item">
{{#if postsMustBeApproved}}
|
{{#if isApproved}}
<a href="#" class="unapprove-link goto-edit">{{_ "unapprove"}}</a>
{{else}}
<a href="#" class="approve-link goto-edit">{{_ "approve"}}</a>
{{/if}}
{{#if showApprove}}
| <a href="#" class="approve-link goto-edit">{{_ "approve"}}</a>
{{/if}}
{{#if showUnapprove}}
| <a href="#" class="unapprove-link goto-edit">{{_ "unapprove"}}</a>
{{/if}}
| {{_ "score"}}: {{shortScore}}, {{_ "clicks"}}: {{clickCount}}, {{_ "views"}}: {{viewCount}}
</div>

View file

@ -1,9 +1,9 @@
Template[getTemplate('postAdmin')].helpers({
postsMustBeApproved: function () {
return !!getSetting('requirePostsApproval');
showApprove: function () {
return this.status == STATUS_PENDING;
},
isApproved: function(){
return this.status == STATUS_APPROVED;
showUnapprove: function(){
return !!getSetting('requirePostsApproval') && this.status == STATUS_APPROVED;
},
shortScore: function(){
return Math.floor(this.score*1000)/1000;

View file

@ -1,3 +1,3 @@
<template name="postAuthor">
<a class="post-author" href="{{profileUrl userId}}">{{userName userId}}</a>
<a class="post-author" href="{{profileUrl userId}}">{{displayName userId}}</a>
</template>

View file

@ -1,7 +1,6 @@
<template name="postDiscuss">
<a class="discuss-link go-to-comments action" href="/posts/{{_id}}">
<a class="discuss-link go-to-comments action" href="/posts/{{_id}}" title="{{_ 'discuss'}}">
<i class="action-icon icon-comment"></i>
<span class="action-count">{{commentCount}}</span>
<span class="action-label">{{_ 'discuss'}}</span>
</a>
</template>

View file

@ -0,0 +1,3 @@
<template name="postRank">
<div class="post-rank-inner"><span>{{oneBasedRank}}</span></div>
</template>

View file

@ -0,0 +1,7 @@
Template[getTemplate('postRank')].helpers({
oneBasedRank: function(){
if (typeof this.rank !== 'undefined') {
return this.rank + 1;
}
}
});

View file

@ -1,14 +1,11 @@
<template name="postUpvote">
<div class="post-rank"><span>{{oneBasedRank}}</span></div>
{{#if upvoted}}
<span class="upvote-link voted action">
<span class="upvote-link voted action" title="{{_ "upvoted"}}">
<i class="icon-check action-icon"></i>
<span class="action-label">{{_ "upvoted"}}</span>
</span>
{{else}}
<a class="upvote-link not-voted action" href="#">
<a class="upvote-link not-voted action" href="#" title="{{_ "upvote_"}}">
<i class="icon-up action-icon"></i>
<span class="action-label">{{_ "upvote_"}}</span>
</a>
{{/if}}
</template>

View file

@ -3,10 +3,6 @@ Template[getTemplate('postUpvote')].helpers({
var user = Meteor.user();
if(!user) return false;
return _.include(this.upvoters, user._id);
},
oneBasedRank: function(){
if(typeof this.rank !== 'undefined')
return this.rank + 1;
}
});

View file

@ -1,7 +1,7 @@
<template name="post_edit">
<div class="grid grid-module">
{{> quickForm collection="Posts" doc=post id="editPostForm" template="telescope" label-class="control-label" input-col-class="controls" type="method" meteormethod="editPost"}}
{{> quickForm collection="Posts" doc=post id="editPostForm" template="telescope" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editPost"}}
</div>
<div class="grid grid-module">

View file

@ -2,8 +2,9 @@ AutoForm.hooks({
editPostForm: {
before: {
editPost: function(doc, template) {
editPost: function(modifier) {
console.log(modifier)
console.log(template)
var post = doc;
// ------------------------------ Checks ------------------------------ //
@ -15,7 +16,7 @@ AutoForm.hooks({
// ------------------------------ Callbacks ------------------------------ //
// run all post edit client callbacks on post object successively
// run all post edit client callbacks on modifier object successively
post = postEditClientCallbacks.reduce(function(result, currentFunction) {
return currentFunction(result);
}, post);
@ -24,12 +25,12 @@ AutoForm.hooks({
}
},
onSuccess: function(operation, post, template) {
onSuccess: function(operation, post) {
trackEvent("edit post", {'postId': post._id});
Router.go('post_page', {_id: post._id});
},
onError: function(operation, error, template) {
onError: function(operation, error) {
console.log(error)
flashMessage(error.reason.split('|')[0], "error"); // workaround because error.details returns undefined
clearSeenMessages();

View file

@ -1,5 +1,5 @@
<template name="post_item">
<div class="post {{rankClass}} {{#if sticky}}sticky{{/if}} {{inactiveClass}}" id="{{_id}}">
<div class="post {{#if sticky}}sticky{{/if}} {{inactiveClass}} {{postClass}}" id="{{_id}}">
{{#each postModules}}
<div class="{{moduleClass}}">
{{> UI.dynamic template=getTemplate data=..}}

View file

@ -19,5 +19,15 @@ Template[getTemplate('post_item')].helpers({
},
moduleClass: function () {
return camelToDash(this.template) + ' post-module';
},
postClass: function () {
var post = this;
var postAuthorClass = "author-"+post.author;
var postClass = postClassCallbacks.reduce(function(result, currentFunction) {
return currentFunction(post, result);
}, postAuthorClass);
return postClass;
}
});

View file

@ -1,13 +1,13 @@
<template name="posts_list">
{{> UI.dynamic template=postsListIncoming data=incoming}}
<div class="posts-wrapper grid grid-module">
<div class="posts list">
{{#each posts}}
{{> UI.dynamic template=postsListIncoming data=incoming}}
<div class="posts list {{postsLayout}}">
{{#each postsCursor}}
{{> UI.dynamic template=before_post_item}}
{{> UI.dynamic template=post_item}}
{{> UI.dynamic template=after_post_item}}
{{/each}}
</div>
{{> UI.dynamic template=postsLoadMore}}
</div>
{{> UI.dynamic template=postsLoadMore}}
</template>

View file

@ -1,4 +1,11 @@
Template[getTemplate('posts_list')].created = function() {
Session.set('listPopulatedAt', new Date());
};
Template[getTemplate('posts_list')].helpers({
postsLayout: function () {
return getSetting('postsLayout', 'posts-list');
},
description: function () {
var controller = Iron.controller();
if (typeof controller.getDescription === 'function')
@ -13,14 +20,15 @@ Template[getTemplate('posts_list')].helpers({
after_post_item: function () {
return getTemplate('after_post_item');
},
posts : function () {
if(this.postsList){ // XXX
this.postsList.rewind();
var posts = this.postsList.map(function (post, index, cursor) {
postsCursor : function () {
if (this.postsCursor) { // not sure why this should ever be undefined, but it can apparently
var posts = this.postsCursor.map(function (post, index, cursor) {
post.rank = index;
return post;
});
return posts;
} else {
console.log('postsCursor not defined')
}
},
postsLoadMore: function () {
@ -29,8 +37,4 @@ Template[getTemplate('posts_list')].helpers({
postsListIncoming: function () {
return getTemplate('postsListIncoming');
}
});
Template[getTemplate('posts_list')].created = function() {
Session.set('listPopulatedAt', new Date());
};
});

View file

@ -0,0 +1,13 @@
<template name="postsLoadMore">
{{#if postsReady}}
{{#if hasPosts}}
{{#if hasMorePosts}}
<a class="more-button" href="#"><span>{{_ "load_more"}}</span></a>
{{/if}}
{{else}}
<div class="no-posts">{{_ "sorry_we_couldnt_find_any_posts"}}</div>
{{/if}}
{{else}}
<div class="loading-module">{{> spinner}}</div>
{{/if}}
</template>

View file

@ -0,0 +1,21 @@
Template[getTemplate('postsLoadMore')].helpers({
postsReady: function () {
return this.postsReady;
},
hasPosts: function () {
return !!this.postsCursor.count();
}
});
Template[getTemplate('postsLoadMore')].events({
'click .more-button': function (event, instance) {
event.preventDefault();
if (this.controllerInstance) {
// controller is a template
this.loadMoreHandler(this.controllerInstance);
} else {
// controller is router
this.loadMoreHandler();
}
}
});

View file

@ -1,6 +1,6 @@
<template name="post_page">
<div class="single-post grid">
<div class="posts">
<div class="posts posts-list">
{{> UI.dynamic template=post_item}}
</div>
{{#if body}}

View file

@ -2,9 +2,9 @@ AutoForm.hooks({
submitPostForm: {
before: {
submitPost: function(doc, template) {
submitPost: function(doc) {
template.$('button[type=submit]').addClass('loading');
this.template.$('button[type=submit]').addClass('loading');
var post = doc;
@ -26,23 +26,23 @@ AutoForm.hooks({
}
},
onSuccess: function(operation, post, template) {
template.$('button[type=submit]').removeClass('loading');
onSuccess: function(operation, post) {
this.template.$('button[type=submit]').removeClass('loading');
trackEvent("new post", {'postId': post._id});
Router.go('post_page', {_id: post._id});
if (post.status === STATUS_PENDING) {
flashMessage(i18n.t('thanks_your_post_is_awaiting_approval'), 'success');
}
Router.go('post_page', {_id: post._id});
},
onError: function(operation, error, template) {
template.$('button[type=submit]').removeClass('loading');
onError: function(operation, error) {
this.template.$('button[type=submit]').removeClass('loading');
flashMessage(error.message.split('|')[0], 'error'); // workaround because error.details returns undefined
clearSeenMessages();
// $(e.target).removeClass('disabled');
if (error.error == 603) {
var dupePostId = error.reason.split('|')[1];
Router.go('/posts/'+dupePostId);
Router.go('post_page', {_id: dupePostId});
}
}

View file

@ -1,11 +0,0 @@
<template name="postsLoadMore">
{{#if hasMorePosts}}
<a class="more-button grid-module" href="{{loadMoreUrl}}"><span>{{_ "load_more"}}</span></a>
{{else}}
{{#unless ready}}
<div class="grid loading-module">
{{> spinner}}
</div>
{{/unless}}
{{/if}}
</template>

View file

@ -1,11 +0,0 @@
Template[getTemplate('postsLoadMore')].helpers({
hasMorePosts: function(){
// as long as we ask for N posts and all N posts showed up, then keep showing the "load more" button
return parseInt(Session.get('postsLimit')) == this.postCount
},
loadMoreUrl: function () {
var count = parseInt(Session.get('postsLimit')) + parseInt(getSetting('postsPerPage', 10));
var categorySegment = Session.get('categorySlug') ? Session.get('categorySlug') + '/' : '';
return '/' + Session.get('view') + '/' + categorySegment + count;
}
});

View file

@ -0,0 +1,4 @@
<template name="users">
<h2 class="user-table-heading">All Users</h2>
{{> reactiveTable settings=settings}}
</template>

View file

@ -0,0 +1,23 @@
Template[getTemplate('users')].helpers({
settings: function() {
return {
collection: 'all-users',
rowsPerPage: 20,
showFilter: true,
fields: [
{ key: 'avatar', label: '', tmpl: Template.users_list_avatar, sortable: false },
{ key: 'username', label: 'Username', tmpl: Template.users_list_username },
{ key: 'profile.name', label: 'Display Name' },
{ key: 'profile.email', label: 'Email', tmpl: Template.users_list_email },
{ key: 'createdAt', label: 'Member Since', tmpl: Template.users_list_created_at, sort: 'descending' },
{ key: 'postCount', label: 'Posts' },
{ key: 'commentCount', label: 'Comments' },
{ key: 'karma', label: 'Karma', fn: function(val){return Math.round(100*val)/100} },
{ key: 'inviteCount', label: 'Invites' },
{ key: 'isInvited', label: 'Invited', fn: function(val){return val ? 'Yes':'No'} },
{ key: 'isAdmin', label: 'Admin', fn: function(val){return val ? 'Yes':'No'} },
{ key: 'actions', label: 'Actions', tmpl: Template.users_list_actions, sortable: false }
]
};
}
});

View file

@ -0,0 +1,21 @@
<template name="users_list_actions">
<ul>
<li>
{{#if isInvited}}
<a class="uninvite-link" href="#">{{_ "uninvite"}}</a>
{{else}}
<a href="#" class="invite-link">{{_ "invite"}}</a>
{{/if}}
</li>
<li>
{{#if userIsAdmin}}
<a class="unadmin-link" href="#">{{_ "unadmin"}}</a>
{{else}}
<a href="#" class="admin-link">{{_ "make_admin"}}</a>
{{/if}}
</li>
<li>
<a class="delete-link" href="#">{{_ "delete_user"}}</a>
</li>
</ul>
</template>

View file

@ -0,0 +1,36 @@
Template[getTemplate('users_list_actions')].helpers({
isInvited: function() {
return this.isInvited;
},
userIsAdmin: function(){
return isAdmin(this);
},
});
Template[getTemplate('users_list_actions')].events({
'click .invite-link': function(e){
e.preventDefault();
Meteor.call('inviteUser', { userId : this._id });
},
'click .uninvite-link': function(e){
e.preventDefault();
Meteor.users.update(this._id,{
$set:{
isInvited: false
}
});
},
'click .admin-link': function(e){
e.preventDefault();
updateAdmin(this._id, true);
},
'click .unadmin-link': function(e){
e.preventDefault();
updateAdmin(this._id, false);
},
'click .delete-link': function(e){
e.preventDefault();
if(confirm(i18n.t("are_you_sure_you_want_to_delete")+getDisplayName(this)+"?"))
Meteor.users.remove(this._id);
}
});

View file

@ -0,0 +1,3 @@
<template name="users_list_avatar">
{{> avatar user=this shape="circle"}}
</template>

View file

@ -0,0 +1,4 @@
<template name="users_list_created_at">
<div class="date">{{formatDate createdAt 'LL'}}</div>
<div class="time-ago">{{timeAgo createdAt}}</div>
</template>

View file

@ -0,0 +1,3 @@
<template name="users_list_email">
<a href="mailto:{{profile.email}}">{{profile.email}}</a>
</template>

View file

@ -0,0 +1,3 @@
<template name="users_list_username">
<a href="{{profileUrl this}}">{{username}}</a>
</template>

View file

@ -1,7 +1,7 @@
<template name="user_email">
<div class="grid-small grid-block dialog user-edit">
{{#with user}}
<div>
<div class="finish-signup-message">
{{_ "please_fill_in_your_email_below_to_finish_signing_up"}}
</div>
<form>

View file

@ -1,48 +0,0 @@
<template name="users">
<div class="grid grid-module">
<div class="user-table grid-block">
<h2>{{_ "users"}}</h2>
<div class="filter-sort">
<p class="filter">
<span>{{_ "filter_by"}}: </span>
<a class="{{activeClass 'all'}}" href="{{filterBy 'all'}}">{{_ "all"}}</a>
<a class="{{activeClass 'invited'}}" href="{{filterBy 'invited'}}">{{_ "invited"}}</a>
<a class="{{activeClass 'uninvited'}}" href="{{filterBy 'uninvited'}}">{{_ "uninvited"}}</a>
<a class="{{activeClass 'admin'}}" href="{{filterBy 'admin'}}">{{_ "admin"}}</a>
</p>
<p class="sort">
<span>{{_ "sort_by"}}: </span>
<a class="{{activeClass 'createdAt'}}" href="{{sortBy 'createdAt'}}">{{_ "created"}}</a>
<a class="{{activeClass 'karma'}}" href="{{sortBy 'karma'}}">{{_ "karma"}}</a>
<a class="{{activeClass 'username'}}" href="{{sortBy 'username'}}">{{_ "username"}}</a>
<a class="{{activeClass 'postCount'}}" href="{{sortBy 'postCount'}}">{{_ "posts"}}</a>
<a class="{{activeClass 'commentCount'}}" href="{{sortBy 'commentCount'}}">{{_ "comments_"}}</a>
<a class="{{activeClass 'invitedCount'}}" href="{{sortBy 'invitedCount'}}">{{_ "invitedcount"}}</a>
</p>
</div>
<table>
<thead>
<tr>
<td colspan="2">{{_ "name"}}</td>
<td>{{_ "member_since"}}</td>
<td>{{_ "posts"}}</td>
<td>{{_ "comments_"}}</td>
<td>{{_ "karma"}}</td>
<td>{{_ "invites"}}</td>
<td>{{_ "invited"}}</td>
<td>{{_ "admin"}}</td>
<td>{{_ "actions"}}</td>
</tr>
</thead>
<tbody>
{{#each users}}
{{> UI.dynamic template=user_item}}
{{/each}}
</tbody>
</table>
<div class="grid more-button {{#if allPostsLoaded}} hidden {{/if}}">
<a class="more-link" href="{{loadMoreUrl}}">{{_ "load_more"}}</a>
</div>
</div>
</div>
</template>

View file

@ -1,23 +0,0 @@
Template[getTemplate('users')].helpers({
user_item: function () {
return getTemplate('user_item');
},
loadMoreUrl: function(){
var count = parseInt(Session.get('usersLimit')) + 20;
return '/all-users/' + count + '?filterBy='+this.filterBy+'&sortBy='+this.sortBy;
},
allPostsLoaded: function () {
return false;
//TODO: hide load more button when all users have been loaded
},
activeClass: function (link) {
if(link == this.filterBy || link == this.sortBy)
return "active";
},
sortBy: function (parameter) {
return "?filterBy="+this.filterBy+"&sortBy="+parameter;
},
filterBy: function (parameter) {
return "?filterBy="+parameter+"&sortBy="+this.sortBy;
}
});

View file

@ -1,85 +1,96 @@
CommentSchemaObject = {
commentSchemaObject = {
_id: {
type: String,
optional: true
type: String,
optional: true
},
parentCommentId: {
type: String,
optional: true
type: String,
optional: true,
autoform: {
editable: true,
omit: true
}
},
createdAt: {
type: Date,
optional: true
type: Date,
optional: true
},
postedAt: { // for now, comments are always created and posted at the same time
type: Date,
optional: true
type: Date,
optional: true
},
body: {
type: String
type: String,
autoform: {
editable: true
}
},
htmlBody: {
type: String,
optional: true
type: String,
optional: true
},
baseScore: {
type: Number,
decimal: true,
optional: true
type: Number,
decimal: true,
optional: true
},
score: {
type: Number,
decimal: true,
optional: true
type: Number,
decimal: true,
optional: true
},
upvotes: {
type: Number,
optional: true
type: Number,
optional: true
},
upvoters: {
type: [String], // XXX
optional: true
type: [String], // XXX
optional: true
},
downvotes: {
type: Number,
optional: true
},
downvoters: {
type: [String], // XXX
optional: true
type: [String], // XXX
optional: true
},
author: {
type: String,
optional: true
type: String,
optional: true
},
inactive: {
type: Boolean,
optional: true
type: Boolean,
optional: true
},
postId: {
type: String, // XXX
optional: true
type: String, // XXX
optional: true,
autoform: {
editable: true,
omit: true
}
},
userId: {
type: String, // XXX
optional: true
type: String, // XXX
optional: true
},
isDeleted: {
type: Boolean,
optional: true
type: Boolean,
optional: true
}
};
// add any extra properties to CommentSchemaObject (provided by packages for example)
// add any extra properties to commentSchemaObject (provided by packages for example)
_.each(addToCommentsSchema, function(item){
CommentSchemaObject[item.propertyName] = item.propertySchema;
commentSchemaObject[item.propertyName] = item.propertySchema;
});
Comments = new Meteor.Collection("comments");
CommentSchema = new SimpleSchema(CommentSchemaObject);
commentSchema = new SimpleSchema(commentSchemaObject);
Comments.attachSchema(CommentSchema);
Comments.attachSchema(commentSchema);
Comments.deny({
update: function(userId, post, fieldNames) {
@ -230,7 +241,13 @@ Meteor.methods({
// if user is not admin, clear restricted properties
if (!hasAdminRights) {
delete comment.userId;
_.keys(comment).forEach(function (propertyName) {
var property = commentSchemaObject[propertyName];
if (!property || !property.autoform || !property.autoform.editable) {
console.log("// Disallowed property detected: "+propertyName+" (nice try!)");
delete comment[propertyName]
}
});
}
// if no userId has been set, default to current user id

View file

@ -16,6 +16,11 @@ var eventSchema = new SimpleSchema({
important: { // marking an event as important means it should never be erased
type: Boolean,
optional: true
},
properties: {
type: Object,
optional: true,
blackbox: true
}
});

View file

@ -3,10 +3,6 @@
// ----------------------------------------- Schema ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //
SimpleSchema.extendOptions({
editable: Match.Optional(Boolean) // editable: true means the field can be edited by the document's owner
});
postSchemaObject = {
_id: {
type: String,
@ -32,7 +28,6 @@ postSchemaObject = {
},
url: {
type: String,
label: "URL",
optional: true,
autoform: {
editable: true,
@ -42,8 +37,6 @@ postSchemaObject = {
title: {
type: String,
optional: false,
label: "Title",
editable: true,
autoform: {
editable: true
}
@ -51,7 +44,6 @@ postSchemaObject = {
body: {
type: String,
optional: true,
editable: true,
autoform: {
editable: true,
rows: 5
@ -207,9 +199,9 @@ _.each(addToPostSchema, function(item){
Posts = new Meteor.Collection("posts");
PostSchema = new SimpleSchema(postSchemaObject);
postSchema = new SimpleSchema(postSchemaObject);
Posts.attachSchema(PostSchema);
Posts.attachSchema(postSchema);
// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- Helpers ----------------------------------------- //
@ -343,12 +335,16 @@ submitPost = function (post) {
score: 0,
inactive: false,
sticky: false,
status: getDefaultPostStatus(),
postedAt: new Date()
status: getDefaultPostStatus()
};
post = _.extend(defaultProperties, post);
// if post is approved but doesn't have a postedAt date, give it a default date
// note: pending posts get their postedAt date only once theyre approved
if (post.status == STATUS_APPROVED && !post.postedAt)
post.postedAt = new Date();
// clean up post title
post.title = cleanUp(post.title);
@ -381,7 +377,6 @@ submitPost = function (post) {
// ----------------------------------------- Methods ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //
postClicks = [];
postViews = [];
Meteor.methods({
@ -434,12 +429,15 @@ Meteor.methods({
// userId
// sticky (default to false)
// if user is not admin, clear restricted properties
// if user is not admin, go over each schema property and throw an error if it's not editable
if (!hasAdminRights) {
delete post.status;
delete post.postedAt;
delete post.userId;
delete post.sticky;
_.keys(post).forEach(function (propertyName) {
var property = postSchemaObject[propertyName];
if (!property || !property.autoform || !property.autoform.editable) {
console.log('//' + i18n.t('disallowed_property_detected') + ": " + propertyName);
throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + propertyName);
}
});
}
// if no post status has been set, set it now
@ -455,9 +453,10 @@ Meteor.methods({
return submitPost(post);
},
editPost: function (post, modifier, postId) {
editPost: function (modifier, postId) {
var user = Meteor.user();
var user = Meteor.user(),
hasAdminRights = isAdmin(user);
// ------------------------------ Checks ------------------------------ //
@ -465,6 +464,21 @@ Meteor.methods({
if (!user || !can.edit(user, Posts.findOne(postId)))
throw new Meteor.Error(601, i18n.t('sorry_you_cannot_edit_this_post'));
// if user is not admin, go over each schema property and throw an error if it's not editable
if (!hasAdminRights) {
// loop over each operation ($set, $unset, etc.)
_.each(modifier, function (operation) {
// loop over each property being operated on
_.keys(operation).forEach(function (propertyName) {
var property = postSchemaObject[propertyName];
if (!property || !property.autoform || !property.autoform.editable) {
console.log('//' + i18n.t('disallowed_property_detected') + ": " + propertyName);
throw new Meteor.Error("disallowed_property", i18n.t('disallowed_property_detected') + ": " + propertyName);
}
});
});
}
// ------------------------------ Callbacks ------------------------------ //
// run all post submit server callbacks on modifier successively
@ -508,6 +522,17 @@ Meteor.methods({
set.postedAt = new Date();
var result = Posts.update(post._id, {$set: set}, {validate: false});
// --------------------- Server-Side Async Callbacks --------------------- //
if (Meteor.isServer) {
Meteor.defer(function () { // use defer to avoid holding up client
// run all post submit server callbacks on post object successively
post = postApproveCallbacks.reduce(function(result, currentFunction) {
return currentFunction(result);
}, post);
});
}
}else{
flashMessage('You need to be an admin to do that.', "error");
}
@ -528,23 +553,11 @@ Meteor.methods({
var view = {_id: postId, userId: this.userId, sessionId: sessionId};
if(_.where(postViews, view).length == 0){
postViews.push(view);
Posts.update(postId, { $inc: { viewCount: 1 }});
postViews.push(view);
Posts.update(postId, { $inc: { viewCount: 1 }});
}
},
increasePostClicks: function(postId, sessionId){
this.unblock();
// only let clients increment a post's click counter once per session
var click = {_id: postId, userId: this.userId, sessionId: sessionId};
if(_.where(postClicks, click).length == 0){
postClicks.push(click);
Posts.update(postId, { $inc: { clickCount: 1 }});
}
},
deletePostById: function(postId) {
// remove post comments
// if(!this.isSimulation) {

View file

@ -1,7 +1,6 @@
settingsSchemaObject = {
title: {
type: String,
label: "Title",
optional: true,
autoform: {
group: 'general'
@ -10,7 +9,6 @@ settingsSchemaObject = {
siteUrl: {
type: String,
optional: true,
label: 'Site URL',
autoform: {
group: 'general',
instructions: 'Your site\'s URL (with trailing "/"). Will default to Meteor.absoluteUrl()'
@ -18,7 +16,6 @@ settingsSchemaObject = {
},
tagline: {
type: String,
label: "Tagline",
optional: true,
autoform: {
group: 'general'
@ -26,7 +23,6 @@ settingsSchemaObject = {
},
description: {
type: String,
label: "Description",
optional: true,
autoform: {
group: 'general',
@ -36,7 +32,6 @@ settingsSchemaObject = {
},
requireViewInvite: {
type: Boolean,
label: "Require invite to view",
optional: true,
autoform: {
group: 'invites',
@ -45,7 +40,6 @@ settingsSchemaObject = {
},
requirePostInvite: {
type: Boolean,
label: "Require invite to post",
optional: true,
autoform: {
group: 'invites',
@ -61,22 +55,6 @@ settingsSchemaObject = {
leftLabel: "Require Posts Approval"
}
},
// nestedComments: {
// type: Boolean,
// label: "Enable nested comments",
// optional: true,
// autoform: {
// group: 'comments'
// }
// },
// redistributeKarma: {
// type: Boolean,
// label: "Enable redistributed karma",
// optional: true,
// autoform: {
// group: 'general'
// }
// },
defaultEmail: {
type: String,
optional: true,
@ -119,6 +97,18 @@ settingsSchemaObject = {
})
}
},
postsLayout: {
type: String,
optional: true,
autoform: {
group: 'posts',
instructions: 'The layout used for post lists',
options: [
{value: 'posts-list', label: 'List'},
{value: 'posts-grid', label: 'Grid'}
]
}
},
postInterval: {
type: Number,
optional: true,
@ -183,6 +173,13 @@ settingsSchemaObject = {
group: 'logo'
}
},
faviconUrl: {
type: String,
optional: true,
autoform: {
group: 'logo'
}
},
language: {
type: String,
defaultValue: 'en',
@ -191,10 +188,10 @@ settingsSchemaObject = {
group: 'general',
instructions: 'The app\'s language. Defaults to English.',
options: function () {
var languages = _.map(TAPi18n.languages_available_for_project, function (item, key) {
var languages = _.map(TAPi18n.getLanguages(), function (item, key) {
return {
value: key,
label: item[0]
label: item.name
}
});
return languages
@ -204,7 +201,6 @@ settingsSchemaObject = {
backgroundCSS: {
type: String,
optional: true,
label: "Background CSS",
autoform: {
group: 'extras',
instructions: 'CSS code for the <body>\'s "background" property',
@ -239,12 +235,27 @@ settingsSchemaObject = {
// type: 'color'
}
},
fontUrl: {
type: String,
optional: true,
autoform: {
group: 'fonts',
instructions: '@import URL (e.g. https://fonts.googleapis.com/css?family=Source+Sans+Pro)'
}
},
fontFamily: {
type: String,
optional: true,
autoform: {
group: 'fonts',
instructions: 'font-family (e.g. "Source Sans Pro", sans-serif)'
}
},
headerTextColor: {
type: String,
optional: true,
autoform: {
group: 'colors',
// type: 'color'
group: 'colors'
}
},
twitterAccount: {
@ -280,7 +291,7 @@ settingsSchemaObject = {
optional: true,
autoform: {
group: 'extras',
instructions: 'Footer content (accepts HTML).',
instructions: 'Footer content (accepts Markdown).',
rows: 5
}
},
@ -316,11 +327,34 @@ settingsSchemaObject = {
debug: {
type: Boolean,
optional: true,
label: 'Debug Mode',
autoform: {
group: 'debug',
instructions: 'Enable debug mode for more details console logs'
}
},
authMethods: {
type: [String],
optional: true,
autoform: {
group: 'auth',
editable: true,
noselect: true,
options: [
{
value: 'email',
label: 'Email/Password'
},
{
value: 'twitter',
label: 'Twitter'
},
{
value: 'facebook',
label: 'Facebook'
}
],
instructions: 'Authentication methods (default to email only)'
}
}
};

View file

@ -1,5 +1,5 @@
var Schema = {};
var userSchemaObj = {
var userSchemaObject = {
_id: {
type: String,
optional: true
@ -44,11 +44,10 @@ var userSchemaObj = {
};
// add any extra properties to postSchemaObject (provided by packages for example)
_.each(addToUserSchema, function(item){
userSchemaObj[item.propertyName] = item.propertySchema;
userSchemaObject[item.propertyName] = item.propertySchema;
});
Schema.User = new SimpleSchema(userSchemaObj);
Schema.User = new SimpleSchema(userSchemaObject);
// Meteor.users.attachSchema(Schema.User);

View file

@ -213,6 +213,18 @@
"you_must_be_logged_in": "Трябва да сте влезнали в системата.",
"are_you_sure": "Сигурни ли сте?",
"please_log_in_first": "Моля първо влезте в системата",
"sign_in_sign_up_with_twitter": "Влезте/Регистрирай се с Twitter",
"load_more": "Зареди повече"
"sign_in_sign_up_with_twitter": "Влезте/Регистрирайте се с Twitter",
"load_more": "Зареди повече",
"most_popular_posts": "Най-популярни публикации в момента.",
"newest_posts": "Най-нови публикации.",
"highest_ranked_posts_ever": "Топ публикации за всички времена.",
"the_profile_of": "Профилът на",
"posts_awaiting_moderation": "Публикации очакващи модерация.",
"future_scheduled_posts": "Планирани публикации.",
"users_dashboard": "Потребителски панел.",
"telescope_settings_panel": "Telescope настройки.",
"various_utilities": "Други услуги."
}

307
i18n/el.i18n.json Normal file
View file

@ -0,0 +1,307 @@
{
//Navigation
"menu": "Μενού",
"view": "Προβολή",
"top": "Κορυφαία",
"new": "Νέα",
"best": "Καλύτερα",
"digest": "Περίληψη",
"users": "Χρήστες",
"settings": "Ρυθμίσεις",
"admin": "Διαχειριστής",
"post": "Δημοσίευση",
"toolbox": "Εργαλειοθήκη",
"sign_up_sign_in": "Εγγραφή/Σύνδεση",
"my_account": "Ο λογαριασμός μου",
"view_profile": "Προβολή προφίλ",
"edit_account": "Επεξεργασία λογαριασμού",
//Main
"new_posts": "Νέες δημοσιέυσεις",
// Settings Schema
"title": "Τίτλος",
"description": "Περιγραφή",
"siteUrl": "URL Ιστοσελίδας",
"tagline": "Ετικέτα",
"requireViewInvite": "Να απαιτείται πρόσκληση για προβολή",
"requirePostInvite": "Να απαιτείται πρόσκληση για δημοσίευση",
"requirePostsApproval": "Να απαιτείται έγκριση των δημοσιεύσεων",
"defaultEmail": "Προεπιλεγμένο Email",
"scoreUpdateInterval": "Χρόνος ανανέωσης Σκορ",
"defaultView": "Προεπιλεγμένη Προβολή",
"postInterval": "Χρόνος ανανέωσης δημοσίευσης",
"commentInterval": "Χρόνος ανανέωσης σχολίου",
"maxPostsPerDay": "Μέγιστες δημοσιεύσεις ανα ημέρα",
"startInvitesCount": "Invites Start Count",
"postsPerPage": "Δημοσιεύσεις ανα ημέρα",
"logoUrl": "URL Λογότυπου",
"logoHeight": "Υψος Λογότυπου",
"logoWidth": "Πλάτος Λογότυπου",
"language": "Γλώσσα",
"backgroundCSS": "Background CSS",
"buttonColor": "Χρώμα κουμπιού",
"buttonTextColor": "Χρώμα κειμένου κουμπιού",
"headerColor": "Χρώμα Επικεφαλίδας",
"headerTextColor": "Χρώμα κειμένου Επικεφαλίδας",
"twitterAccount": "Λογαριασμός Twitter",
"googleAnalyticsId": "Google Analytics ID",
"mixpanelId": "Mixpanel ID",
"clickyId": "Clicky ID",
"footerCode": "Footer Code",
"extraCode": "Extra Code",
"emailFooter": "Email Footer",
"notes": "Σημειώσεις",
"debug": "Debug Mode",
"fontUrl": "Font URL",
"fontFamily": "Font Family",
"authMethods": "Authentication Methods",
"faviconUrl": "Favicon URL",
"mailURL": "MailURL",
"postsLayout": "Στύλ Δημοσιεύσεων",
// Settings Fieldsets
"general": "Γενικά",
"invites": "Προσκλήσεις",
"email": "Email",
"scoring": "Σκορ",
"posts": "Δημοσιέυσεις",
"comments": "Σχόλια",
"logo": "Λογότυπο",
"extras": "Extras",
"colors": "Χρώματα",
"integrations": "Προσθήκες",
// Settings Help Text
// Post Schema
"createdAt": "Δημιουργήθηκε στις",
"postedAt": "Δημοσιεύθηκε στις",
"url": "URL",
"title": "Τίτλος",
"body": "Κείμενο",
"htmlBody": "HTML κείμενο",
"viewCount": "Πλήθος προβολών",
"commentCount": "Πλήθος σχολίων",
"commenters": "Σχολιαστές",
"lastCommentedAt": "Τελευταίο σχόλιο στις",
"clickCount": "Πλήθος κλικ",
"baseScore": "Βασικό σκορ",
"upvotes": "Υπερψηφισμοί",
"upvoters": "Υπερψηφιστές",
"downvotes": "Καταψηφισμοί",
"downvoters": "Καταψηφιστές",
"score": "Σκορ",
"status": "Κατάσταση",
"sticky": "Προτεινόμενα",
"inactive": "Ανενεργά",
"author": "Δημιουργός",
"userId": "Χρήστης",
"sorry_we_couldnt_find_any_posts": "Μας συγχωρείτε, δεν βρήκαμε καμιά δημοσίευση.",
//Commments
"your_comment_has_been_deleted": "Το σχόλιο σας έχει διαγραφεί.",
"comment_": "Σχόλιο",
"delete_comment": "Διαγραφή σχολίου",
"add_comment": "Νέο σχόλιο",
"upvote": "Υπερ",
"downvote": "Κατά",
"link": "Σύνδεσμος",
"edit": "Επεξεργασία",
"reply": "Απάντηση",
"no_comments": "Κανένα σχόλιο.",
//Errors
"you_are_already_logged_in": "Είστε ήδη συνδεδεμένος",
"sorry_this_is_a_private_site_please_sign_up_first": "Μας συγχωρείτε αλλα πρέπει να εγγραφείτε για να συνεχίσετε.",
"thanks_for_signing_up": "Ευχαριστούμε για την εγγραφή σας!",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "Δυστυχώς χρειάζεστε πρόσκληση για να εγγραφείτε. Θα σας ειδοποιήσουμε μόλις ανοίξουν πάλι οι εγγραφές.",
"sorry_you_dont_have_the_rights_to_view_this_page": "Δεν έχετε δικαίωμα να δείτε αυτήν την σελίδα.",
"sorry_you_do_not_have_the_rights_to_comments": "Δεν έχετε δικαίωμα να κάνετε σχόλιο.",
"not_found": "Δεν βρέθηκε!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "Αυτό που ψάχνετε δεν είναι εδώ!",
"disallowed_property_detected": "Παράνομη παράμετρος!",
//Notifications
"no_notifications": "Καμία ειδοποίηση",
"1_notification": "1 ειδοποίηση",
"notifications": "ειδοποίησεις",
"mark_all_as_read": "Μάρκαρε τα όλα ότι τα διάβασες",
// Post deleted
"your_post_has_been_deleted": "Η δημοσίευση σου έχει διαγραφεί.",
// Post submit & edit
"created": "Δημιουργήθηκε",
"title": "Τίτλος",
"suggest_title": "Πρότεινε ενα τίτλο",
"url": "URL",
"short_url": "Short URL",
"body": "Κείμενο",
"category": "Κατηγορία",
"inactive_": "Ανενεργό?",
"sticky_": "Προτεινόμενο?",
"submission_date": "Ημερομηνία Υποβολής",
"submission_time": "Ώρα Υποβολής",
"date": "Ημερομηνία",
"submission": "Υποβολή",
"note_this_post_is_still_pending_so_it_has_no_submission_timestamp_yet": "Note: this post is still pending so it has no submission timestamp yet.",
"user": "Χρήστης",
"status_": "Κατάσταση",
"approved": "Εγκρίθηκε",
"rejected": "Απορρίφθηκε",
"delete_post": "Διαγραφή δημοσίευσης",
"thanks_your_post_is_awaiting_approval": "Ευχαριστούμε, η δημοσίευση αναμένει εγκριση.",
"sorry_couldnt_find_a_title": "Συγγνώμη, ο τίτλος δεν βρέθηκε ",
"please_fill_in_an_url_first": "Παρακαλώ συμπληρώστε το URL πρώτα!",
// Post item
"share": "Μοιράσου",
"discuss": "Συζύτησε",
"upvote_": "Μου αρέσει",
"sticky": "Προτεινόμενο",
"status": "κατάσταση",
"votes": "Ψήφοι",
"basescore": "Βασικό Σκορ",
"score": "σκορ",
"clicks": "κλικ",
"views": "προβολές",
"inactive": "ανενεργό",
"comment": "σχόλιο",
"comments": "σχόλια",
"point": "πόντος",
"points": "πόντους",
//User /client/views/users/account/user_account.html
"please_complete_your_profile_below_before_continuing": "Παρακαλώ συμπληρώστε το προφίλ σας πριν συνεχισετε.",
"account": "Λογαριασμός",
"username": "Ονομα χρήστη",
"display_name": "Παρατσούκλι",
"email": "Email",
"bio": "Βιογραφία",
"twitter_username": "Ονομα χρήστη Twitter",
"github_username": "Ονομα χρήστη GitHub",
"site_url" : "URL Ιστοσελίδας",
"password": "κωδικός",
"change_password": "Αλλαγή κωδικού?",
"old_password": "Παλιός κωδικός",
"new_password": "Νέος κωδικός",
"email_notifications": "Ειδοποιήσεις μέσω Email",
"new_users" : "Νέοι Χρήστες",
"new_posts": "Νέες δημοσιεύσεις",
"comments_on_my_posts": "Σχόλια στις δημοσιέυσεις μου",
"replies_to_my_comments": "Απαντήσεις στα σχόλια μου",
"forgot_password": "Ξέχασες τον κωδικό σου;",
"profile_updated": "Το προφίλ ενημερώθηκε",
"please_fill_in_your_email_below_to_finish_signing_up": "Παρακαλώ συμπλήρωσε το email για να ολοκληρώσεις την εγγραφή σου.",
"invite": "Προσκληση",
"uninvite": "Διαγραφή πρόσκλησης",
"make_admin": "Δικαίωμα διαχειριστή",
"unadmin": "Διαγραφή δικαίωματος διαχειριστή",
"delete_user": "Διαγραφή χρήστη",
"are_you_sure_you_want_to_delete": "Είσαι σίγουρος για την διαγραφή",
"reset_password": "Επαναφορά κωδικού",
"password_reset_link_sent": "Στείλαμε σύνδεσμο επαναφοράς κωδικου στο email!",
"name": "Όνομα",
"posts": "Δημοσιεύσεις",
"comments_": "Σχόλια",
"karma": "Karma",
"is_invited": "Έχει προσκληση?",
"is_admin": "Είναι διαχειριστής?",
"delete": "Διαγραφή",
"member_since": "Μέλος από",
"edit_profile": "Επεξεργασία Προφίλ",
"sign_in": "Σύνδεση",
"sign_in_": "Σύνδεση!",
"sign_up_": "Εγγραφή!",
"dont_have_an_account": "Δεν έχεις λογαριασμό;",
"already_have_an_account": "Έχεις ήδη λογαριασμό;",
"sign_up": "Εγγραφλη",
"please_fill_in_all_fields": "Παρακαλώ συμπληρώστε τα πεδία",
"invite_": "Πρόσκληση ",
"left": " αριστερά",
"invite_none_left": "Πρόσκληση (κανένας αριστερά)",
"all": "Όλους",
"invited": "Αυτούς που έχουν πρόσκληση",
"uninvited": "Αυτούς που ΔΕΝ έχουν πρόσκληση",
"filter_by": "Δείξε ",
"sort_by": "Ταξινόμηση",
//helpers
"sorry_you_do_not_have_access_to_this_page": "Συγγνώμη, δεν έχετε πρόσβαση σε αυτήν τη σελίδα",
"please_sign_in_first": "Πρέπει να συνδεθείς πρώτα.",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Συγγνώμη, πρέπει να είσαι διαχειριστής για να δείς αυτήν τη σελίδα.",
"sorry_you_dont_have_permissions_to_add_new_items": "Συγγνώμη, Συγγνώμη, δεν έχετε δικαίωμα να προσθέσετε νέα στοιχεία.",
"sorry_you_cannot_edit_this_post": "Συγγνώμη, δεν μπορείς να επεξεργαστείς αυτήν την δημοσίευση.",
"sorry_you_cannot_edit_this_comment": "Συγγνώμη, δεν μπορείς να επεξεργαστείς συτό το σχόλιο.",
//Collections
"you_need_to_login_and_be_an_admin_to_add_a_new_category": "Πρέπει να συνδεθείς για να προσθέσεις νέα κατηγορία.",
"you_need_to_login_or_be_invited_to_post_new_comments": "Πρέπει να συνδεθείς ή να έχεις πρόσκληση για να κάνεις σχόλια.",
"please_wait": "Παρακαλώ περιμένετε ",
"seconds_before_commenting_again": " δευτερόλεπτα πριν μπορείτε να ξανα σχολιάσετε.",
"your_comment_is_empty": "Το σχόλιό σας είναι άδειο.",
"you_dont_have_permission_to_delete_this_comment": "Συγγνώμη, Συγγνώμη, δεν έχετε δικαίωμα να διαγράψετε αυτό το σχόλιο.",
"you_need_to_login_or_be_invited_to_post_new_stories": "Πρέπει να συνδεθείς ή να έχεις πρόσκληση για να δημοσιέυσεις.",
"please_fill_in_a_headline": "Παρακαλώ συμπληρώστε την επικεφαλίδα",
"this_link_has_already_been_posted": "Αυτός ο σύνδεσμος υπάρχει ήδη",
"sorry_you_cannot_submit_more_than": "Δεν μπορείς να υποβάλεις περισσότερα από ",
"posts_per_day": " σχόλια την ημέρα",
"someone_replied_to_your_comment_on": "Κάποιος απάντησε στο σχόλιό σου",
"has_replied_to_your_comment_on": " απάντησε στο σχόλιό σου",
"read_more": "Διάβασε περισσότερα",
"a_new_comment_on_your_post": "Νέο σχόλιο στη δημοσίευση σου",
"you_have_a_new_comment_by": "Νέο σχόλιο από",
"on_your_post": " στη δημοσίευση σου",
"has_created_a_new_post": " έκανε μια νέα δημοσίευση",
"your_account_has_been_approved": "Ο λογαριασμό σου έχει εγκριθεί.",
"welcome_to": "Καλωσορίσατε στο ",
"start_posting": "Ξεκινήστε να δημοσιεύετε.",
// Translation needed (found during migration to tap:i18n)
"please_fill_in_a_title": "Παρακαλώ συμπληρώστε τον τίτλο",
"seconds_before_posting_again": " δευτερόλεπτα πριν ξανα δημοσιεύσετε",
"upvoted": "Υπερψηφισμένο",
"posted_date": "Ημερομηνία δημοσίευσης",
"posted_time": "Ωρα δημοσίευσης",
"profile": "Προφίλ",
"sign_out": "Αποσύνδεση",
"you_ve_been_signed_out": "Εχετε αποσυνδεθεί!",
"invitedcount": "Πλήθος προσκλήσεων",
"invites": "Προσκλήσεις",
"invited": "Προσκεκλημενος?",
"admin": "Διαχειριστής",
"actions": "Ενέργειες",
"invites_left": "Προσκλήσεις που απομενουν",
"id": "ID",
"name": "Όνομα:",
"bio": "Βιογραφία:",
"github": "GitHub",
"site": "Site",
"upvoted_posts": "Δημοσιεύσεις που μου αρέσουν",
"downvoted_posts": "Δημοσιεύσεις που ΔΕΝ μου αρέσουν",
"mark_as_read": "To διάβασα",
//Common
"pending": "Εκκρεμούν",
"loading": "Περιμένετε...",
"submit": "Υποβολή",
"you_must_be_logged_in": "Πρέπει να συνδεθείτε.",
"are_you_sure": "Είστε σίγουρος?",
"please_log_in_first": "Πρέπει να συνδεθείτε πρώτα.",
"please_log_in_to_comment": "Πρέπει να συνδεθείτε για να κάνετε σχόλιο.",
"sign_in_sign_up_with_twitter": "Εγγραφείτε με το Twitter σας",
"load_more": "Περισσότερα",
"most_popular_posts": "Οι πιο δημοφιλής δημοσιεύσεις.",
"newest_posts": "Οι πιο καινούριες δημοσιεύσεις.",
"highest_ranked_posts_ever": "Οι πιο υπερψηφισμένες δημοσιεύσεις.",
"the_profile_of": "Το προφίλ του",
"posts_awaiting_moderation": "Δημοσιεύσεις που αναμένουν έγγριση.",
"future_scheduled_posts": "Μελλοντικές δημοσιεύσεις.",
"users_dashboard": "Πίνακας Χρηστών.",
"telescope_settings_panel": "Γενικές Ρυθμίσεις.",
"various_utilities": "Διάφορα εργαλεία."
}

View file

@ -11,7 +11,7 @@
"admin": "Admin",
"post": "Post",
"toolbox": "Toolbox",
"sign_up_sign_in": "Sign Up/Sign In",
"sign_up_sign_in": "Register/Sign In",
"my_account": "My Account",
"view_profile": "View Profile",
"edit_account": "Edit Account",
@ -21,6 +21,7 @@
// Settings Schema
"title": "Title",
"description": "Description",
"siteUrl": "Site URL",
"tagline": "Tagline",
"requireViewInvite": "Require Invite to View",
@ -52,14 +53,20 @@
"emailFooter": "Email Footer",
"notes": "Notes",
"debug": "Debug Mode",
"fontUrl": "Font URL",
"fontFamily": "Font Family",
"authMethods": "Authentication Methods",
"faviconUrl": "Favicon URL",
"mailURL": "MailURL",
"postsLayout": "Posts Layout",
// Settings Fieldsets
"general": "General",
"invites": "Invites",
"email": "Email",
"email": "Email",
"scoring": "Scoring",
"posts": "Posts",
"comments": "Comments",
"comments": "Comments",
"logo": "Logo",
"extras": "Extras",
"colors": "Colors",
@ -70,7 +77,7 @@
// Post Schema
"createdAt": "Created At",
"postedAt": "Posted At",
"url": "URL",
"url": "URL",
"title": "Title",
"body": "Body",
"htmlBody": "HTML Body",
@ -90,6 +97,7 @@
"inactive": "Inactive",
"author": "Author",
"userId": "User",
"sorry_we_couldnt_find_any_posts": "Sorry, we couldn't find any posts.",
//Commments
"your_comment_has_been_deleted": "Your comment has been deleted.",
@ -105,14 +113,15 @@
//Errors
"you_are_already_logged_in": "You are already logged in",
"sorry_this_is_a_private_site_please_sign_up_first": "Sorry, this is a private site. Please sign up first.",
"thanks_for_signing_up": "Thanks for signing up!",
"sorry_this_is_a_private_site_please_sign_up_first": "Sorry, this is a private site. Please register first.",
"thanks_for_signing_up": "Thanks for registering!",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "The site is currently invite-only, but we will let you know as soon as a spot opens up.",
"sorry_you_dont_have_the_rights_to_view_this_page": "Sorry, you don't have the rights to view this page.",
"sorry_you_do_not_have_the_rights_to_comments": "Sorry, you do not have the rights to leave comments at this time.",
"not_found": "Not Found!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "We're sorry; whatever you were looking for isn't here..",
"disallowed_property_detected": "Disallowed property detected",
//Notifications
"no_notifications": "No notifications",
"1_notification": "1 notification",
@ -184,7 +193,7 @@
"replies_to_my_comments": "Replies to my comments",
"forgot_password": "Forgot password?",
"profile_updated": "Profile updated",
"please_fill_in_your_email_below_to_finish_signing_up": "Please fill in your email below to finish signing up.",
"please_fill_in_your_email_below_to_finish_signing_up": "Please fill in your email below to finish the registration.",
"invite": "Invite",
"uninvite": "Uninvite",
"make_admin": "Make admin",
@ -204,10 +213,10 @@
"edit_profile": "Edit profile",
"sign_in": "Sign In",
"sign_in_": "Sign in!",
"sign_up_": "Sign up!",
"sign_up_": "Register!",
"dont_have_an_account": "Don't have an account?",
"already_have_an_account": "Already have an account?",
"sign_up": "Sign Up",
"sign_up": "Register",
"please_fill_in_all_fields": "Please fill in all fields",
"invite_": "Invite ",
"left": " left",
@ -255,8 +264,6 @@
"upvoted": "Upvoted",
"posted_date": "Posted Date",
"posted_time": "Posted Time",
"posted_date": "Posted Date",
"posted_time": "Posted Time",
"profile": "Profile",
"sign_out": "Sign Out",
"you_ve_been_signed_out": "You've been signed out. Come back soon!",
@ -283,15 +290,15 @@
"are_you_sure": "Are you sure?",
"please_log_in_first": "Please log in first.",
"please_log_in_to_comment": "Please log in to comment.",
"sign_in_sign_up_with_twitter": "Sign In/Sign Up with Twitter",
"load_more": "Load more",
"sign_in_sign_up_with_twitter": "Register/Sign Up with Twitter",
"load_more": "Load More",
"most_popular_posts": "The most popular posts right now.",
"newest_posts": "The newest posts.",
"highest_ranked_posts_ever": "The all-time highest-ranked posts.",
"the_profile_of": "The profile of",
"posts_awaiting_moderation": "Posts awaiting moderation.",
"future_scheduled_posts": "Future scheduled posts.",
"users_dashboard": "Users dashboard.",

View file

@ -1,8 +1,8 @@
{
//Navigation
"view": "Vista",
"menu": "Menu",
"top": "Todos",
"view": "Explorar",
"menu": "Menú",
"top": "Top",
"new": "Nuevos",
"digest": "Resumen",
"users": "Usuarios",
@ -10,7 +10,7 @@
"admin": "Admin",
"post": "Post",
"toolbox": "Herramientas",
"sign_up_sign_in": "Connectarse/Crear una cuenta",
"sign_up_sign_in": "Registrarse/Iniciar sesión",
"my_account": "Mi Cuenta",
"view_profile": "Ver perfil",
"edit_account": "Editar cuenta",
@ -21,7 +21,7 @@
//Commments
"your_comment_has_been_deleted": "Tu comentario ha sido borrado",
"comment_": "Comentario",
"delete_comment": "Borrar el comentario",
"delete_comment": "Borrar comentario",
"add_comment": "Añadir comentario",
"upvote": "Voto Positivo",
"downvote": "Voto Negativo",
@ -34,10 +34,10 @@
"you_are_already_logged_in": "Ya estás conectado",
"sorry_this_is_a_private_site_please_sign_up_first": "Lo sentimos pero esta pagina es privada. Por favor, conéctese para verla",
"thanks_for_signing_up": "Gracias por registrarte",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "El sitio solo es accesible mediante invitación, pero te lo haremos saber tan pronto como abra al público.",
"sorry_you_dont_have_the_rights_to_view_this_page": "Lo sentimos pero no tienes los derechos suficientes para ver esta pagina",
"not_found": "No encontramos nada!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "Lo sentimos pero aqui no hay nada.. ",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "El sitio solo es accesible mediante invitación, pero te haremos saber pronto cuando este disponible para el público.",
"sorry_you_dont_have_the_rights_to_view_this_page": "Lo sentimos pero no tienes los permisos suficientes para ver esta pagina",
"not_found": "¡No encontramos nada!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "Lo sentimos pero aqui no hay nada... ",
//Notifications
"no_notifications": "Ninguna notificación",
@ -56,8 +56,8 @@
"short_url": "URL Corta",
"body": "Descripción",
"category": "Categoría",
"inactive_": "Inactivo?",
"sticky_": "Pegado?",
"inactive_": "Inactivo",
"sticky_": "Destacar",
"submission_date": "Fecha de entrega",
"submission_time": "Hora de entrega",
"date": "Fecha",
@ -66,17 +66,17 @@
"user": "Usuario",
"status_": "Estado",
"approved": "Aprobado",
"rejected": "Rechezado",
"rejected": "Rechazado",
"delete_post": "Borrar este post",
"thanks_your_post_is_awaiting_approval": "Gracias, su post esta esperando validación.",
"sorry_couldnt_find_a_title": "Lo sentimos, impossible de encontrar este título.",
"please_fill_in_an_url_first": "Tienes que poner una URL.",
"thanks_your_post_is_awaiting_approval": "Gracias, su post esta esperando aprobación.",
"sorry_couldnt_find_a_title": "Lo sentimos, imposible de encontrar este título.",
"please_fill_in_an_url_first": "Tienes que introducir una URL.",
// Post item
"share": "Compartir",
"discuss": "Comentar",
"upvote_": "Votar",
"sticky": "Pegado",
"sticky": "Destacado",
"status": "Estado",
"votes": "votos",
"basescore": "baseScore",
@ -106,21 +106,21 @@
"replies_to_my_comments": "Respuestas a mis comentarios",
"forgot_password": "Olvidaste tu contraseña?",
"profile_updated": "Perfil actualizado",
"please_fill_in_your_email_below_to_finish_signing_up": "Por favor, introduzca su email para terminar de inscribirse.",
"please_fill_in_your_email_below_to_finish_signing_up": "Por favor, introduzca su email para terminar de registrarse.",
"invite": "Invitar",
"uninvite": "Cancelar la invitación",
"make_admin": "Hacer admin",
"unadmin": "Borrar de admin",
"delete_user": "Borrar usuario",
"are_you_sure_you_want_to_delete": "Está seguro de que desea eliminar",
"are_you_sure_you_want_to_delete": "¿Está seguro de que desea eliminar?",
"reset_password": "Restablecer contraseña",
"password_reset_link_sent": "Enlace de restablecimiento de contraseña enviado!!",
"password_reset_link_sent": "Enlace de restablecimiento de contraseña enviado a su email.",
"name": "Nombre",
"posts": "Posts",
"comments_": "Comentarios",
"karma": "Karma",
"is_invited": "Esta invitado?",
"is_admin": "Es admin?",
"is_invited": "¿Esta invitado?",
"is_admin": "¿Es admin?",
"delete": "Borrar",
"member_since": "Miembro desde",
"edit_profile": "Modificar el perfil",
@ -141,10 +141,10 @@
"sort_by": "Ordenar por",
//Helpers
"sorry_you_do_not_have_access_to_this_page": "Lo sentimos, usted no tiene acceso a esta página",
"please_sign_in_first": "Tiene que registrarse primero.",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Lo sentimos, usted tiene que ser un administrador para ver esta página.",
"sorry_you_dont_have_permissions_to_add_new_items": "Lo sentimos, usted no tiene permisos para agregar nuevos elementos.",
"sorry_you_do_not_have_access_to_this_page": "Lo sentimos, no tienes acceso a esta página",
"please_sign_in_first": "Tienes que registrarte primero.",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Lo sentimos, tienes que ser un administrador para ver esta página.",
"sorry_you_dont_have_permissions_to_add_new_items": "Lo sentimos, no tiene permisos para agregar nuevos elementos.",
"sorry_you_cannot_edit_this_post": "Lo sentimos, no puede editar este post.",
"sorry_you_cannot_edit_this_comment": "Lo sentimos, no puede editar este comentario.",
@ -171,37 +171,35 @@
"start_posting": "Empezar a publicar",
// Translation needed (found during migration to tap:i18n)
"please_fill_in_a_title": "Please fill in a title",
"seconds_before_posting_again": " seconds before posting again",
"upvoted": "Upvoted",
"posted_date": "Posted Date",
"posted_time": "Posted Time",
"posted_date": "Posted Date",
"posted_time": "Posted Time",
"profile": "Profile",
"sign_out": "Sign Out",
"invitedcount": "InvitedCount",
"invites": "Invites",
"invited": "Invited?",
"admin": "Admin?",
"actions": "Actions",
"invites_left": "invites left",
"please_fill_in_a_title": "Por favor, agrega un título",
"seconds_before_posting_again": "segundos antes de postear de nuevo",
"upvoted": "Voto a favor",
"posted_date": "Fecha de publicación",
"posted_time": "Tiempo de publicación",
"profile": "Perfil",
"sign_out": "Cerrar sesión",
"invitedcount": "Total de invitados",
"invites": "Invitaciones",
"invited": "¿Invitado?",
"admin": "¿Administrador?",
"actions": "Acciones",
"invites_left": "Invitaciones pendientes",
"id": "ID",
"name": "Name:",
"bio": "Bio:",
"name": "Nombre:",
"bio": "Biografía:",
"github": "GitHub",
"site": "Site",
"upvoted_posts": "Upvoted Posts",
"downvoted_posts": "Downvoted Posts",
"mark_as_read": "Mark as read",
"site": "Sitio",
"upvoted_posts": "Posts votados a favor",
"downvoted_posts": "Posts votados en contra",
"mark_as_read": "Marcar como leído",
//Common
"pending": "Pendiente",
"loading": "Cargando...",
"submit": "Enviar",
"you_must_be_logged_in": "Debe estar conectado",
"are_you_sure": "¿Está seguro? ",
"you_must_be_logged_in": "Debes estar conectado",
"are_you_sure": "¿Estás seguro? ",
"please_log_in_first": "Por favor, inicia sesión",
"sign_in_sign_up_with_twitter": "Inicia sesión/Inscribete con Twitter",
"load_more": "Cargar más"
"sign_in_sign_up_with_twitter": "Regístrate/Inicia sesión con Twitter",
"load_more": "Mostrar más"
}

302
i18n/pl.i18n.json Normal file
View file

@ -0,0 +1,302 @@
{
//Navigation
"menu": "Menu",
"view": "Widok",
"top": "Na topie",
"new": "Najnowsze",
"best": "Najlepsze",
"digest": "Dzisiaj",
"users": "Użytkownicy",
"settings": "Ustawienia",
"admin": "Admin",
"post": "Nowy temat",
"toolbox": "Narzędzia",
"sign_up_sign_in": "Zarejestruj/Zaloguj",
"my_account": "Moje konto",
"view_profile": "Profil",
"edit_account": "Edytuj konto",
//Main
"new_posts": "Nowe Posty",
// Settings Schema
"title": "Tytuł",
"siteUrl": "URL strony",
"tagline": "Podtytuł",
"requireViewInvite": "Wymagaj zaproszenia żeby przeglądać",
"requirePostInvite": "Wymagaj zaproszenia żeby pisać",
"requirePostsApproval": "Zatwierdzanie nowych postów",
"defaultEmail": "Standardowy Email",
"scoreUpdateInterval": "Częstotliwość przeliczania punktów",
"defaultView": "Standardowy widok",
"postInterval": "Interwał czasowy dla nowych postów",
"commentInterval": "Interwał czasowy dla nowych komentarzy",
"maxPostsPerDay": "Maksymalna liczba postów w jednym dniu",
"startInvitesCount": "Licznik zaproszeń",
"postsPerPage": "Postów na stronę",
"logoUrl": "URL Logo",
"logoHeight": "Wysokość Logo",
"logoWidth": "Szerokość Logo",
"language": "Język",
"backgroundCSS": "Tło CSS",
"buttonColor": "Kolor przycisków",
"buttonTextColor": "Kolor tekstu na przyciskach",
"headerColor": "Kolor dla nagłówka",
"headerTextColor": "Kolor tekstu dla nagłówka",
"twitterAccount": "Konto Twitter",
"googleAnalyticsId": "Google Analytics ID",
"mixpanelId": "Mixpanel ID",
"clickyId": "Clicky ID",
"footerCode": "Kod w stopce",
"extraCode": "Dodatkowy kod",
"emailFooter": "Stopka Email",
"notes": "Notatki",
"debug": "Debug Mode",
// Settings Fieldsets
"general": "Główne",
"invites": "Zaproszenia",
"email": "Email",
"scoring": "Scoring",
"posts": "Posty",
"comments": "Comments",
"logo": "Logo",
"extras": "Extras",
"colors": "Kolory",
"integrations": "Integracje",
// Settings Help Text
// Post Schema
"createdAt": "Utworzony",
"postedAt": "Dodany",
"url": "URL",
"title": "Tytuł",
"body": "Treść",
"htmlBody": "Treść HTML",
"viewCount": "Liczba odświeżeń",
"commentCount": "Liczba komentarzy",
"commenters": "Komentujący",
"lastCommentedAt": "Ostatnio komentował",
"clickCount": "Liczba kliknięć",
"baseScore": "Bazowy wynik",
"upvotes": "Pozytywne",
"upvoters": "Głosujący pozytywnie",
"downvotes": "Negatywne",
"downvoters": "Głosujący negatywnie",
"score": "Wynik",
"status": "Status",
"sticky": "Przyklejony",
"inactive": "Nieaktywny",
"author": "Autor",
"userId": "Użytkownik",
"sorry_we_couldnt_find_any_posts": "Przepraszamy, ale w tej chwili nie ma tutaj żadnych postów.",
//Commments
"your_comment_has_been_deleted": "Twój komentarz został usunięty.",
"comment_": "Komentuj",
"delete_comment": "Usuń komentarz",
"add_comment": "Dodaj komentarz",
"upvote": "plus",
"downvote": "minus",
"link": "link",
"edit": "Edytuj",
"reply": "Odpowiedz",
"no_comments": "Brak komentarzy.",
//Errors
"you_are_already_logged_in": "Jesteś już zalogowany",
"sorry_this_is_a_private_site_please_sign_up_first": "Musisz się najpierw zarejestrować.",
"thanks_for_signing_up": "Dzięki za rejestrację!",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "Tą stronę mogą oglądać jedynie zaproszone osoby",
"sorry_you_dont_have_the_rights_to_view_this_page": "Niestety nie masz odpowiednich praw dostępu żeby widzieć tą stronę.",
"sorry_you_do_not_have_the_rights_to_comments": "Niestety nie masz odpowiednich praw dostępu żeby móc dodawać komentarze.",
"not_found": "Nie znaleziono!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "Niestety nie ma tutaj tego czego szukałeś...",
//Notifications
"no_notifications": "Brak powiadomień",
"1_notification": "1 powiadomienie",
"notifications": "powiadomień",
"mark_all_as_read": "Oznacz wszystkie jako przeczytane",
// Post deleted
"your_post_has_been_deleted": "Twój post został usunięty.",
// Post submit & edit
"created": "Utworzone",
"title": "Tytuł",
"suggest_title": "Zasugeruj tytuł",
"url": "URL",
"short_url": "Krótki URL",
"body": "Body",
"category": "Kategoria",
"categories": "Kategorie",
"inactive_": "Nieaktywny?",
"sticky_": "Przyklejony?",
"submission_date": "Data utworzenia",
"submission_time": "Godzina utworzenia",
"date": "Data",
"submission": "Wpis",
"note_this_post_is_still_pending_so_it_has_no_submission_timestamp_yet": "Ten post ciągle czeka na zatwierdzenie.",
"user": "Użytkownik",
"status_": "Status",
"approved": "Zaakceptowany",
"rejected": "Odrzucony",
"delete_post": "Usuń post",
"thanks_your_post_is_awaiting_approval": "Twój post czeka na zatwierdzenie.",
"sorry_couldnt_find_a_title": "Podaj tytuł...",
"please_fill_in_an_url_first": "Podaj URL",
// Post item
"share": "Udostępnij",
"discuss": "Komentuj",
"upvote_": "Plus",
"sticky": "Przyklejony",
"status": "status",
"votes": "głosy",
"basescore": "wynik bazowy",
"score": "wynik",
"clicks": "kliknięcia",
"views": "wyświetlenia",
"inactive": "nieaktywny",
"comment": "komentarz",
"comments": "komentarze",
"point": "punkt",
"points": "punktów",
//User /client/views/users/account/user_account.html
"please_complete_your_profile_below_before_continuing": "Uzupełnij profil.",
"account": "Konto",
"username": "Nick",
"display_name": "Nazwa wyświetlana",
"email": "Email",
"bio": "Bio",
"twitter_username": "Twitter",
"github_username": "GitHub",
"site_url" : "Strona WWW",
"password": "Hasło",
"change_password": "Zmienić hasło?",
"old_password": "Stare hasło",
"new_password": "Nowe hasło",
"email_notifications": "Notyfikacje email",
"new_users" : "Nowi użytkownicy",
"new_posts": "Nowe posty",
"comments_on_my_posts": "Komentarze do moich postów",
"replies_to_my_comments": "Odpowiedzi na moje komentarze",
"forgot_password": "Zapomniałeś hasło?",
"profile_updated": "Profil został zaktualizowany",
"please_fill_in_your_email_below_to_finish_signing_up": "Uzupełnij email.",
"invite": "Zaproś",
"uninvite": "Wyproś",
"make_admin": "Mianuj admina",
"unadmin": "Zdejmij admina",
"delete_user": "Usuń użytkownika",
"are_you_sure_you_want_to_delete": "Jesteś pewny, że chcesz usunąć ",
"reset_password": "Resetuj hasło",
"password_reset_link_sent": "Link z nowym hasłem został wysłany!",
"name": "Imię",
"posts": "Posty",
"comments_": "Komentarze",
"karma": "Karma",
"is_invited": "Czy jest zaproszony?",
"is_admin": "Czy jest adminem?",
"delete": "Usuń",
"member_since": "Zarejestrowany od",
"edit_profile": "Edytuj profil",
"sign_in": "Zaloguj",
"sign_in_": "Zaloguj!",
"sign_up_": "Zarejestruj!",
"dont_have_an_account": "Nie masz konta?",
"already_have_an_account": "Masz już konto?",
"sign_up": "Zarejestruj",
"please_fill_in_all_fields": "Uzupełnij pola",
"invite_": "Zaproś ",
"left": " left",
"invite_none_left": "Zaproszenia (brak)",
"all": "Wszyscy",
"invited": "Zaproszeni",
"uninvited": "Niezaproszeni",
"filter_by": "Filtruj po",
"sort_by": "Sortuj po",
//helpers
"sorry_you_do_not_have_access_to_this_page": "Przepraszamy, nie masz dostępu.",
"please_sign_in_first": "Zaloguj się.",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Musisz być adminem żeby to zobaczyć.",
"sorry_you_dont_have_permissions_to_add_new_items": "Nie masz uprawnień do dodawania.",
"sorry_you_cannot_edit_this_post": "Nie możesz edytować tego postu.",
"sorry_you_cannot_edit_this_comment": "Nie możesz edytować tego komentarza.",
//Collections
"you_need_to_login_and_be_an_admin_to_add_a_new_category": "Musisz się zalogować jako admin aby móc dodawać nowe kategorie.",
"you_need_to_login_or_be_invited_to_post_new_comments": "Musisz być zalogowany lub zaproszony aby dodawaćc nowe komentarze.",
"please_wait": "Proszę czekać ",
"seconds_before_commenting_again": " sekund zanim znowu będziesz móc komentować",
"your_comment_is_empty": "Twój komentarz jest pusty.",
"you_dont_have_permission_to_delete_this_comment": "Nie możesz usunąć tego komentarza.",
"you_need_to_login_or_be_invited_to_post_new_stories": "Musisz być zalogowany lub zaproszony aby dodawać nowe posty.",
"please_fill_in_a_headline": "Please fill in a headline",
"this_link_has_already_been_posted": "Ten link już istnieje",
"sorry_you_cannot_submit_more_than": "Nie możesz dodawać więcej niż ",
"posts_per_day": " postów na dzień",
"someone_replied_to_your_comment_on": "Ktoś odpowiedział na twój komentarz w",
"has_replied_to_your_comment_on": " odpowiedział na twój komentarz w",
"read_more": "Czytaj dalej",
"a_new_comment_on_your_post": "Nowy komentarz",
"you_have_a_new_comment_by": "Pojawił się nowy komentarz ",
"on_your_post": " dla twojego posta",
"has_created_a_new_post": " utworzył nowy post",
"your_account_has_been_approved": "Twoje konto zostało zaakceptowane.",
"welcome_to": "Witaj na ",
"start_posting": "Zacznij pisać.",
// Translation needed (found during migration to tap:i18n)
"please_fill_in_a_title": "Wypełnij tytuł",
"seconds_before_posting_again": " sekund zanim znowu będziesz mógł napisać",
"upvoted": "minus",
"posted_date": "Data",
"posted_time": "Godzina",
"posted_date": "Data",
"posted_time": "Godzina",
"profile": "Profil",
"sign_out": "Wyloguj się",
"you_ve_been_signed_out": "Zostałeś prawidłowo wylogowany!",
"invitedcount": "Liczba zaproszeń",
"invites": "Zaproszenia",
"invited": "Zaproszony?",
"admin": "Admin",
"actions": "Akcje",
"invites_left": "zaproszeń pozostało",
"id": "ID",
"name": "Imię:",
"bio": "Bio:",
"github": "GitHub",
"site": "Strona WWW",
"upvoted_posts": "Głosy pozytywne",
"downvoted_posts": "Głosy negatywne",
"mark_as_read": "Oznacz jako przeczytane",
//Common
"pending": "Oczekuje",
"loading": "Ładowanie...",
"submit": "Wyślij",
"you_must_be_logged_in": "Musisz być zalogowany.",
"are_you_sure": "Jesteś pewny?",
"please_log_in_first": "Najpierw się zaloguj.",
"please_log_in_to_comment": "Aby komentować musisz być zalogowany.",
"sign_in_sign_up_with_twitter": "Zarejestruj/Zaloguj się przez Twitter",
"load_more": "Więcej",
"most_popular_posts": "Aktualnie najpopularniejsze posty.",
"newest_posts": "Najnowsze posty.",
"highest_ranked_posts_ever": "Najwyżej oceniane posty wszechczasów.",
"the_profile_of": "Profil",
"posts_awaiting_moderation": "Posty czekające na moderację.",
"future_scheduled_posts": "Posty na przyszłość.",
"users_dashboard": "Pulpit użytkowników.",
"telescope_settings_panel": "Ustawienia.",
"various_utilities": "Narzędzia."
}

301
i18n/pt-BR.i18n.json Normal file
View file

@ -0,0 +1,301 @@
{
//Navigation
"menu": "Menu",
"view": "Visão",
"top": "Topo",
"new": "Novo",
"best": "Melhor",
"digest": "Resumo",
"users": "Usuários",
"settings": "Configurações",
"admin": "Admin",
"post": "Postar",
"toolbox": "Toolbox",
"sign_up_sign_in": "Registrar/Entrar",
"my_account": "Minha Conta",
"view_profile": "Ver Perfil",
"edit_account": "Editar Conta",
//Main
"new_posts": "Novas Postagens",
// Settings Schema
"title": "Título",
"siteUrl": "URL do site",
"tagline": "Descrição",
"requireViewInvite": "Exigir Convite para Ver",
"requirePostInvite": "Exigir Convite para Postar",
"requirePostsApproval": "Exigir Postagens serem Aprovadas",
"defaultEmail": "Email Padrão",
"scoreUpdateInterval": "Definir Intervalo de Atualização",
"defaultView": "Visão Padrão",
"postInterval": "Intervalo de Postagens",
"commentInterval": "Intervalo de Comentários",
"maxPostsPerDay": "Máx de Postagens Por Dia",
"startInvitesCount": "Número Inicial de Convites",
"postsPerPage": "Postagens Por Página",
"logoUrl": "URL do Logo",
"logoHeight": "Altura do Logo",
"logoWidth": "Comprimento do Logo",
"language": "Linguagem",
"backgroundCSS": "Background CSS",
"buttonColor": "Cor do Botão",
"buttonTextColor": "Cor do Texto do Botão",
"headerColor": "Cor do Cabeçalho",
"headerTextColor": "Cor do Texto do Cabeçalho",
"twitterAccount": "Conta do Twitter",
"googleAnalyticsId": "ID do Google Analytics",
"mixpanelId": "ID do Mixpanel",
"clickyId": "ID do Clicky",
"footerCode": "Código para o Rodapé",
"extraCode": "Código Extra",
"emailFooter": "Rodapé do Email",
"notes": "Notas",
"debug": "Modo de Debug",
// Settings Fieldsets
"general": "Geral",
"invites": "Convites",
"email": "Email",
"scoring": "Classificação",
"posts": "Postagens",
"comments": "Comentários",
"logo": "Logo",
"extras": "Extras",
"colors": "Cores",
"integrations": "Integrações",
// Settings Help Text
// Post Schema
"createdAt": "Criado em",
"postedAt": "Postado em",
"url": "URL",
"title": "Título",
"body": "Corpo",
"htmlBody": "Corpo HTML",
"viewCount": "Ver Contagem",
"commentCount": "Contagem de Comentários",
"commenters": "Comentaristas",
"lastCommentedAt": "Comentado por último em",
"clickCount": "Contagem de cliques",
"baseScore": "Classificação Básica",
"upvotes": "Votos Positivos",
"upvoters": "Votadores Positivos",
"downvotes": "Votos Negativos",
"downvoters": "Votadores Negativos",
"score": "Classificação",
"status": "Estado",
"sticky": "Fixo",
"inactive": "Inativo",
"author": "Autor",
"userId": "Usuário",
"sorry_we_couldnt_find_any_posts": "Desculpe, não conseguimos encontrar nenhuma postagem.",
//Commments
"your_comment_has_been_deleted": "Seu comentário foi deletado.",
"comment_": "Comentário",
"delete_comment": "Deletar Comentário",
"add_comment": "Adicionar Comentário",
"upvote": "+",
"downvote": "-",
"link": "link",
"edit": "Editar",
"reply": "Responder",
"no_comments": "Sem comentários.",
//Errors
"you_are_already_logged_in": "Você já está logado",
"sorry_this_is_a_private_site_please_sign_up_first": "Desculpe, mas este é um site privado. Registre-se primeiro.",
"thanks_for_signing_up": "Obrigado por se registrar!",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "O site está atualmente apenas para convidados, mas nós iremos avisá-lo assim que abrirmos ao público geral.",
"sorry_you_dont_have_the_rights_to_view_this_page": "Desculpe, você não pode ver esta página.",
"sorry_you_do_not_have_the_rights_to_comments": "Desculpe, você não pode comentar neste momento.",
"not_found": "Não Encontrado!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "Nos desculpe; o que estava procurando não se encontra aqui...",
//Notifications
"no_notifications": "Sem notificações",
"1_notification": "1 notificação",
"notifications": "notificações",
"mark_all_as_read": "Marcar todas como lidas",
// Post deleted
"your_post_has_been_deleted": "Sua postagem foi deletada.",
// Post submit & edit
"created": "Criado",
"title": "Título",
"suggest_title": "Sugerir título",
"url": "URL",
"short_url": "URL curta",
"body": "Corpo",
"category": "Categoria",
"inactive_": "Inativo?",
"sticky_": "Fixo?",
"submission_date": "Data de Submissão",
"submission_time": "Hora de Submissão",
"date": "Data",
"submission": "Submissão",
"note_this_post_is_still_pending_so_it_has_no_submission_timestamp_yet": "Nota: esta postagem continua pendente e não possui data de submissão ainda.",
"user": "Usuário",
"status_": "Estado",
"approved": "Aprovada",
"rejected": "Rejeitada",
"delete_post": "Deletar Postagem",
"thanks_your_post_is_awaiting_approval": "Obrigado, sua postagem está aguardando aprovação.",
"sorry_couldnt_find_a_title": "Desculpe, não encontramos um título...",
"please_fill_in_an_url_first": "Por favor, inclua a URL antes!",
// Post item
"share": "Compartilhar",
"discuss": "Discutir",
"upvote_": "Votar",
"sticky": "Fixo",
"status": "estado",
"votes": "votos",
"basescore": "classificaçaoBase",
"score": "classificação",
"clicks": "cliques",
"views": "visualizações",
"inactive": "inativo",
"comment": "comentário",
"comments": "comentários",
"point": "ponto",
"points": "pontos",
//User /client/views/users/account/user_account.html
"please_complete_your_profile_below_before_continuing": "Por favor, complete seu perfil abaixo antes de continuar.",
"account": "Conta",
"username": "Nome de usuário",
"display_name": "Nome de exibição",
"email": "Email",
"bio": "Bio",
"twitter_username": "Twitter",
"github_username": "GitHub",
"site_url" : "URL do Site",
"password": "Senha",
"change_password": "Mudar Senha?",
"old_password": "Senha Antiga",
"new_password": "Nova Senha",
"email_notifications": "Notificações por Email",
"new_users" : "Novos usuários",
"new_posts": "Novas Postagens",
"comments_on_my_posts": "Comentários em minhas postagens",
"replies_to_my_comments": "Respostas aos meus comentários",
"forgot_password": "Esqueceu sua senha?",
"profile_updated": "Perfil atualizado",
"please_fill_in_your_email_below_to_finish_signing_up": "Por favor, preencha seu email abaixo para finalizar o registro.",
"invite": "Convite",
"uninvite": "Desconvidar",
"make_admin": "Tornar admin",
"unadmin": "Retirar do admin",
"delete_user": "Deletar Usuário",
"are_you_sure_you_want_to_delete": "Está certo de que deseja deletar ",
"reset_password": "Resetar Senhar",
"password_reset_link_sent": "Link de reset de senha enviado!",
"name": "Nome",
"posts": "Postagens",
"comments_": "Comentários",
"karma": "Carma",
"is_invited": "Foi Convidado?",
"is_admin": "É Admin?",
"delete": "Deletar",
"member_since": "Membro desde",
"edit_profile": "Editar perfil",
"sign_in": "Entrar",
"sign_in_": "Entrar!",
"sign_up_": "Registrar!",
"dont_have_an_account": "Não possui uma conta?",
"already_have_an_account": "Já possui uma conta?",
"sign_up": "Registrar",
"please_fill_in_all_fields": "Por favor, preencha todos os campos",
"invite_": "Convidar ",
"left": " restantes",
"invite_none_left": "Convidar (nenhum restante)",
"all": "Todos",
"invited": "Convidado",
"uninvited": "Desconvidado",
"filter_by": "Filtrar por",
"sort_by": "Distribuir por",
//helpers
"sorry_you_do_not_have_access_to_this_page": "Desculpe, você não possui acesso a esta página",
"please_sign_in_first": "Por favor, entre com sua conta primeiro.",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Desculpe, você precisa ser admin para ver esta página.",
"sorry_you_dont_have_permissions_to_add_new_items": "Desculpe, você não possui permissão para adicionar novos itens.",
"sorry_you_cannot_edit_this_post": "Desculpe, você não pode estar esta postagem.",
"sorry_you_cannot_edit_this_comment": "Desculpe, você não pode editar este comentário.",
//Collections
"you_need_to_login_and_be_an_admin_to_add_a_new_category": "Você precisa se logar e ser um admin para adicionar uma nova categoria.",
"you_need_to_login_or_be_invited_to_post_new_comments": "Você precisa se logar ou ser convidado para postar novos comentários.",
"please_wait": "Por favor aguarde ",
"seconds_before_commenting_again": " segundos antes de comentar novamente",
"your_comment_is_empty": "Seu comentário está vazio.",
"you_dont_have_permission_to_delete_this_comment": "Você não possui permissão para deletar este comentário.",
"you_need_to_login_or_be_invited_to_post_new_stories": "Você precisa se logar ou ser convidado para novas postagens.",
"please_fill_in_a_headline": "Por favor, preencha uma chamada",
"this_link_has_already_been_posted": "Este link já foi publicado",
"sorry_you_cannot_submit_more_than": "Desculpe, você não pode submeter mais do que ",
"posts_per_day": " postagens por dia",
"someone_replied_to_your_comment_on": "Alguém respondeu ao seu comentário em",
"has_replied_to_your_comment_on": " respondeu ao seu comentário em",
"read_more": "Ler mais",
"a_new_comment_on_your_post": "Um novo comentário em sua postagem",
"you_have_a_new_comment_by": "Você possui um novo comentário por ",
"on_your_post": " em sua postagem",
"has_created_a_new_post": " criou uma nova postagem",
"your_account_has_been_approved": "Sua conta foi aprovada.",
"welcome_to": "Bem vindo para ",
"start_posting": "Comece a postar.",
// Translation needed (found during migration to tap:i18n)
"please_fill_in_a_title": "Por favor preencha um título",
"seconds_before_posting_again": " segundos antes de postar novamente",
"upvoted": "Votado",
"posted_date": "Data da Postagem",
"posted_time": "Hora da da Postagem",
"posted_date": "Data da Postagem",
"posted_time": "Hora da da Postagem",
"profile": "Perfil",
"sign_out": "Sair",
"you_ve_been_signed_out": "Você saiu com sucesso. Volte logo!",
"invitedcount": "ContagemConvites",
"invites": "Convites",
"invited": "Convidado?",
"admin": "Admin",
"actions": "Ações",
"invites_left": "invites left",
"id": "ID",
"name": "Nome:",
"bio": "Bio:",
"github": "GitHub",
"site": "Site",
"upvoted_posts": "Postagens votadas",
"downvoted_posts": "Postagens contra",
"mark_as_read": "Marcar como lido",
//Common
"pending": "Pendente",
"loading": "Carregando...",
"submit": "Submeter",
"you_must_be_logged_in": "Você deve estar logado.",
"are_you_sure": "Você está certo?",
"please_log_in_first": "Por favor, entre primeiro.",
"please_log_in_to_comment": "Por favor entre para comentário.",
"sign_in_sign_up_with_twitter": "Registrar/Entrar com Twitter",
"load_more": "Carregar Mais",
"most_popular_posts": "As postagens mais populares neste momento.",
"newest_posts": "As postagens mais novas.",
"highest_ranked_posts_ever": "As melhores postagens de todos os tempos.",
"the_profile_of": "O perfil de",
"posts_awaiting_moderation": "Postagens aguardando moderação.",
"future_scheduled_posts": "Postagens agendadas para o futuro.",
"users_dashboard": "Painel dos usuários.",
"telescope_settings_panel": "Painel de Configurações do Telescope.",
"various_utilities": "Várias utilidades."
}

View file

@ -25,7 +25,7 @@
"upvote": "1",
"downvote": "-1",
"link": "link",
"edit": "Ayarla",
"edit": "Düzenle",
"reply": "Cevap",
"no_comments": "Yorum yok",
@ -35,8 +35,8 @@
"thanks_for_signing_up": "Kayıt olduğunuz için teşekkür ederiz",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "Bu site sadece davetliler için ama bir yer açılınca size haber vereceğiz",
"sorry_you_dont_have_the_rights_to_view_this_page": "Özür dileriz, bu sayfaya erişiminiz yok",
"not_found": "Bulunmadı!",
"ere_sorry_whatever_you_were_looking_for_isnt_here": "Özür dileriz, neye bakıyorsanız burada değil..",
"not_found": "Bulunamadı!",
"ere_sorry_whatever_you_were_looking_for_isnt_here": "Özür dileriz, aradığınız şey burada değil.",
//Notifications
"no_notifications": "Bildirim yok",
@ -61,7 +61,7 @@
"submission_time": "Yayın zamanı",
"date": "Tarih",
"submission": "Yayın",
"note_this_post_is_still_pending_so_it_has_no_submission_timestamp_yet": "Bu paylaşım hala onay bekliyor, onun için yayın tarihi yok",
"note_this_post_is_still_pending_so_it_has_no_submission_timestamp_yet": "Bu paylaşım hala onay bekliyor, bu nedenle henüz yayın tarihi yok",
"user": "Kullanıcı",
"status": "Durum",
"approved": "Onaylandı",
@ -104,11 +104,11 @@
"replies_to_my_comments": "Yorumlarıma cevaplar",
"forgot_password": "Şifreyi unuttunuz mu?",
"profile_updated": "Profil güncellendi",
"please_fill_in_your_email_below_to_finish_signing_up": "Lütfen kaydınızı tamamlamak için aşağıya e-postanızı giriniz",
"please_fill_in_your_email_below_to_finish_signing_up": "Lütfen kaydınızı tamamlamak için aşağıya e-posta adresinizi giriniz",
"invite": "Davet et",
"uninvite": "Daveti geri al",
"make_admin": "Admin yap",
"unadmin": "Admini kaldır",
"unadmin": "Adminliği kaldır",
"delete_user": "Kullanıcıyı sil",
"are_you_sure_you_want_to_delete": "Silmek istediğinize emin misiniz?",
"reset_password": "Şifreyi sıfırla",
@ -139,10 +139,10 @@
"sort_by": "Sıralama kıstası",
//helpers
"sorry_you_do_not_have_access_to_this_page": "Özür dileriz, bu sayfaya erişiminiz yok",
"sorry_you_do_not_have_access_to_this_page": "Özür dileriz, bu sayfaya erişim izniniz yok",
"please_sign_in_first": "Lütfen önce giriş yapın",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Özür dileriz, sadece adminler bu sayfayı görebilir",
"sorry_you_dont_have_permissions_to_add_new_items": "Özür dileriz, yeni birşeyler eklemeye yetkiniz yok",
"sorry_you_dont_have_permissions_to_add_new_items": "Özür dileriz, yeni bir şeyler eklemeye yetkiniz yok",
"sorry_you_cannot_edit_this_post": "Özür dileriz, bu paylaşımı değiştiremezsiniz",
"sorry_you_cannot_edit_this_comment": "Özür dileriz, bu yorumu değiştiremezsiniz",
@ -150,19 +150,19 @@
"you_need_to_login_and_be_an_admin_to_add_a_new_category": "Yeni kategori eklemek için admin olarak giriş yapmanız lazım",
"you_need_to_login_or_be_invited_to_post_new_comments": "Yorum yapmak için giriş yapmanız veya davet edilmeniz lazım",
"please_wait": "Lütfen bekleyin ",
"seconds_before_commenting_again": " saniye daha beklemiz lazım tekrar yorum yapmadan önce",
"seconds_before_commenting_again": " saniye daha beklemeniz lazım tekrar yorum yapmadan önce",
"your_comment_is_empty": "Yorumunuz boş",
"you_dont_have_permission_to_delete_this_comment": "Bu yorumu silmek için izniniz yok",
"you_need_to_login_or_be_invited_to_post_new_stories": "Paylaşım yapmak için giriş yapmanız veya davet edilmeniz lazım",
"you_need_to_login_or_be_invited_to_post_new_stories": "Paylaşım yapmak için giriş yapmanız ya da davet edilmiş olmanız lazım",
"please_fill_in_a_headline": "Lütfen bir başlık girin",
"this_link_has_already_been_posted": "Bu bağlantı daha önce paylaşılmıştı",
"sorry_you_cannot_submit_more_than": "Özür dileriz, bundan daha fazla paylaşamazsınız ",
"sorry_you_cannot_submit_more_than": "Özür dileriz, bu sayıdan daha fazla paylaşamazsınız: ",
"posts_per_day": " paylaşım / gün",
"someone_replied_to_your_comment_on": "Birisi yorumunuza cevap verdi şu konu hakkında",
"has_replied_to_your_comment_on": " yorumunuza cevap verdi şu konu hakkında",
"someone_replied_to_your_comment_on": "Birisi yorumunuza cevap verdi şu konu hakkında: ",
"has_replied_to_your_comment_on": " yorumunuza cevap verdi şu konu hakkında:",
"read_more": "Daha fazla oku",
"a_new_comment_on_your_post": "Yeni bir yorum paylaşımınızda",
"you_have_a_new_comment_by": "Yeni bir yorum geldi ",
"a_new_comment_on_your_post": "Şu paylaşımınıza yeni bir yorum yapıldı: ",
"you_have_a_new_comment_by": "Şu kişiden yeni bir yorum aldınız: ",
"on_your_post": " paylaşımınızda",
"has_created_a_new_post": " yeni bir paylaşım yaptı",
"your_account_has_been_approved": "Hesabınız onaylandı",
@ -171,7 +171,7 @@
// Translation needed (found during migration to tap:i18n)
"please_fill_in_a_title": "Lütfen bir başlık girin",
"seconds_before_posting_again": " saniye kaldı bir paylaşım daha yapmadan önce",
"seconds_before_posting_again": " saniye daha beklemeniz lazım tekrar paylaşım yapmadan önce",
"upvoted": "Yukarı oylandı",
"posted_date": "Paylaşım Tarihi",
"posted_time": "Paylaşım Zamanı",
@ -188,9 +188,9 @@
"bio": "Bio:",
"github": "GitHub",
"site": "Site",
"upvoted_posts": "Yukarı oy alan paylaşımlar",
"upvoted_posts": "Yukarı oy alan paylaşımlar",
"downvoted_posts": "Aşağı oy alan paylaşımlar",
"mark_as_read": "Okundu gibi işaretle",
"mark_as_read": "Okundu olarak işaretle",
//Common
@ -200,7 +200,7 @@
"you_must_be_logged_in": "Giriş yapmanız lazım",
"are_you_sure": "Emin misiniz?",
"please_log_in_first": "Lütfen önce giriş yapın",
"sign_in_sign_up_with_twitter": "Twitter ile kayıt ol/ giriş yap",
"load_more": "Daha Fazla yükle"
"sign_in_sign_up_with_twitter": "Twitter ile kayıt ol/giriş yap",
"load_more": "Daha fazla yükle"
}

301
i18n/vn.i18n.json Normal file
View file

@ -0,0 +1,301 @@
{
//Navigation
"menu": "Danh mục",
"view": "Xem",
"top": "Top",
"new": "New",
"best": "Best",
"digest": "Digest",
"users": "Người dùng",
"settings": "Settings",
"admin": "Admin",
"post": "Bài",
"toolbox": "Toolbox",
"sign_up_sign_in": "Đăng ký/Đăng nhập",
"my_account": "Tài khoản",
"view_profile": "Xem hồ sơ",
"edit_account": "Chỉnh sửa",
//Main
"new_posts": "Bài mới",
// Settings Schema
"title": "Tiêu đề",
"siteUrl": "Địa chỉ URL",
"tagline": "Tagline",
"requireViewInvite": "Require Invite to View",
"requirePostInvite": "Require Invite to Post",
"requirePostsApproval": "Require Posts to be Approved",
"defaultEmail": "Default Email",
"scoreUpdateInterval": "Score Update Interval",
"defaultView": "Default View",
"postInterval": "Post Interval",
"commentInterval": "Comment Interval",
"maxPostsPerDay": "Max Posts Per Day",
"startInvitesCount": "Invites Start Count",
"postsPerPage": "Posts Per Page",
"logoUrl": "Logo URL",
"logoHeight": "Logo Height",
"logoWidth": "Logo Width",
"language": "Language",
"backgroundCSS": "Background CSS",
"buttonColor": "Button Color",
"buttonTextColor": "Button Text Color",
"headerColor": "Header Color",
"headerTextColor": "Header Text Color",
"twitterAccount": "Twitter Account",
"googleAnalyticsId": "Google Analytics ID",
"mixpanelId": "Mixpanel ID",
"clickyId": "Clicky ID",
"footerCode": "Footer Code",
"extraCode": "Extra Code",
"emailFooter": "Email Footer",
"notes": "Notes",
"debug": "Debug Mode",
// Settings Fieldsets
"general": "General",
"invites": "Invites",
"email": "Email",
"scoring": "Scoring",
"posts": "Posts",
"comments": "Comments",
"logo": "Logo",
"extras": "Extras",
"colors": "Colors",
"integrations": "Integrations",
// Settings Help Text
// Post Schema
"createdAt": "Tạo lúc",
"postedAt": "Đăng lúc",
"url": "URL",
"title": "Tiêu đề",
"body": "Nội dung",
"htmlBody": "HTML Body",
"viewCount": "Số lần xem",
"commentCount": "Số lần bình luận",
"commenters": "Bình luận",
"lastCommentedAt": "Bình luận lúc",
"clickCount": "Click Count",
"baseScore": "Base Score",
"upvotes": "Upvotes",
"upvoters": "Upvoters",
"downvotes": "Downvotes",
"downvoters": "Downvoters",
"score": "Score",
"status": "Status",
"sticky": "Sticky",
"inactive": "Inactive",
"author": "Author",
"userId": "User",
"sorry_we_couldnt_find_any_posts": "Xin lỗi, thông tin không được tìm thấy.",
//Commments
"your_comment_has_been_deleted": "Ý kiến của bạn đã được xóa.",
"comment_": "Ý kiến",
"delete_comment": "Xóa ý kiến",
"add_comment": "Thêm ý kiến",
"upvote": "Thích",
"downvote": "Không thích",
"link": "link",
"edit": "Sửa",
"reply": "Trả lời",
"no_comments": "Không ý kiến.",
//Errors
"you_are_already_logged_in": "Bạn đã đăng nhập",
"sorry_this_is_a_private_site_please_sign_up_first": "Xin lỗi, bạn cần đăng ký để xem thông tin.",
"thanks_for_signing_up": "Cám ơn bạn đã đăng ký!",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up": "Trang này hiện chỉ dùng cho những người được mời, chúng tôi sẽ cho bạn biết khi sẵn sàng.",
"sorry_you_dont_have_the_rights_to_view_this_page": "Xin lỗi, bạn không có quyền để xem trang này.",
"sorry_you_do_not_have_the_rights_to_comments": "Xin lỗi, hiện tại bạn không có quyền để đăng ý kiến.",
"not_found": "Không tìm thấy!",
"were_sorry_whatever_you_were_looking_for_isnt_here": "Chúng tôi xin lỗi vì không có thông tin bạn đang tìm kiếm...",
//Notifications
"no_notifications": "Không có thông báo",
"1_notification": "1 thông báo",
"notifications": "Thông báo",
"mark_all_as_read": "Đánh dấu đã đọc",
// Post deleted
"your_post_has_been_deleted": "Bài của bạn đã được xóa.",
// Post submit & edit
"created": "Tạo",
"title": "Tiêu đề",
"suggest_title": "Gợi ý tiêu đề",
"url": "URL",
"short_url": "URL ngắn",
"body": "Nội dung",
"category": "Loại",
"inactive_": "Ngừng kích hoạt?",
"sticky_": "Sticky?",
"submission_date": "Ngày đăng",
"submission_time": "Giờ đăng",
"date": "Ngày",
"submission": "Đăng",
"note_this_post_is_still_pending_so_it_has_no_submission_timestamp_yet": "Lưu ý: bài này đang đợi xét duyệt nên chưa có thời gian đăng bài.",
"user": "Người dùng",
"status_": "Trạng thái",
"approved": "Đồng ý",
"rejected": "Từ chối",
"delete_post": "Xóa bài",
"thanks_your_post_is_awaiting_approval": "Cảm ơn, bài của bạn đang đợi phê duyệt.",
"sorry_couldnt_find_a_title": "Xin lỗi, không có tiêu đề...",
"please_fill_in_an_url_first": "Làm ơn nhập địa chỉ website!",
// Post item
"share": "Chia sẻ",
"discuss": "Bình luận",
"upvote_": "Thích",
"sticky": "Sticky",
"status": "trạng thái",
"votes": "phiếu",
"basescore": "baseScore",
"score": "điểm",
"clicks": "clicks",
"views": "xem",
"inactive": "inactive",
"comment": "ý kiến",
"comments": "ý kiến",
"point": "điểm",
"points": "điểm",
//User /client/views/users/account/user_account.html
"please_complete_your_profile_below_before_continuing": "Xin điền thông tin hồ sơ của bạn để tiếp tục.",
"account": "Tài khoản",
"username": "Tên đăng nhập",
"display_name": "Tên xuất hiện",
"email": "Email",
"bio": "Bio",
"twitter_username": "Tài khoản Twitter",
"github_username": "Tài khoản GitHub",
"site_url" : "Địa chỉ website",
"password": "Mật khẩu",
"change_password": "Thay đổi mật khẩu?",
"old_password": "Mật khẩu cũ",
"new_password": "Mật khẩu mới",
"email_notifications": "Email thông báo",
"new_users" : "Người dùng mới",
"new_posts": "Bài mới",
"comments_on_my_posts": "Bình luận trên bài của tôi",
"replies_to_my_comments": "Trả lời ý kiến của tôi",
"forgot_password": "Quyên mật khẩu?",
"profile_updated": "Cập nhật hồ sơ",
"please_fill_in_your_email_below_to_finish_signing_up": "Xin nhập email của bạn dưới đây để hoàn thành việc đăng ký.",
"invite": "Mời",
"uninvite": "Không mời",
"make_admin": "Thiết lập Admin",
"unadmin": "Ngắt Admin",
"delete_user": "Xóa người dùng",
"are_you_sure_you_want_to_delete": "Bạn có chắc muốn xóa?",
"reset_password": "Thiết lập lại mật khẩu",
"password_reset_link_sent": "Mật khẩu đã được gửi!",
"name": "Tên",
"posts": "Bài",
"comments_": "Ý kiến",
"karma": "Karma",
"is_invited": "Được mời?",
"is_admin": "Admin?",
"delete": "Xóa",
"member_since": "Thành viên từ",
"edit_profile": "Sửa hồ sơ",
"sign_in": "Đăng nhập",
"sign_in_": "Đăng nhập!",
"sign_up_": "Đăng ký!",
"dont_have_an_account": "Bạn không có tài khoản?",
"already_have_an_account": "Bạn đã có tài khoản?",
"sign_up": "Đăng ký",
"please_fill_in_all_fields": "Nhập thông tin",
"invite_": "Mời ",
"left": " left",
"invite_none_left": "Invite (none left)",
"all": "Tất cả",
"invited": "Mời",
"uninvited": "Không mời",
"filter_by": "Lọc theo",
"sort_by": "Sắp xếp theo",
//helpers
"sorry_you_do_not_have_access_to_this_page": "Xin lỗi, bạn không có quyền truy cập vào trang này",
"please_sign_in_first": "Xin đăng nhập trước.",
"sorry_you_have_to_be_an_admin_to_view_this_page": "Xin lỗi, bản phải có quyền Admin để xem trang này.",
"sorry_you_dont_have_permissions_to_add_new_items": "Xin lỗi, bạn không có quyền thêm.",
"sorry_you_cannot_edit_this_post": "Xin lỗi, bạn không thể sửa bài này.",
"sorry_you_cannot_edit_this_comment": "Xin lỗi, bạn không thể sửa ý kiến này.",
//Collections
"you_need_to_login_and_be_an_admin_to_add_a_new_category": "Bạn phải đăng nhập và là Admin để tạo thẻ.",
"you_need_to_login_or_be_invited_to_post_new_comments": "Bạn phải đăng nhập và được mời để đăng ý kiến.",
"please_wait": "Làm ơn đợi ",
"seconds_before_commenting_again": " một vài giây để đăng ý kiến tiếp",
"your_comment_is_empty": "Xin nhập ý kiến.",
"you_dont_have_permission_to_delete_this_comment": "Bạn không có quyền để xóa ý kiến này.",
"you_need_to_login_or_be_invited_to_post_new_stories": "Bạn phải đăng nhập và được mời để đăng bài mới.",
"please_fill_in_a_headline": "Xin nhập thông tin",
"this_link_has_already_been_posted": "Đường dẫn này đã được đăng",
"sorry_you_cannot_submit_more_than": "Xin lỗi, bạn không thể đăng nhiều hơn ",
"posts_per_day": " bài mỗi ngày",
"someone_replied_to_your_comment_on": "Có người trả lời ý kiến của bạn",
"has_replied_to_your_comment_on": " đã trả lời ý kiến của bạn",
"read_more": "Xem tiếp",
"a_new_comment_on_your_post": "Có ý kiến mới trên bài của bạn",
"you_have_a_new_comment_by": "Bạn có ý kiến mới bởi ",
"on_your_post": " trên bài của bạn",
"has_created_a_new_post": " đã bạo bài mới",
"your_account_has_been_approved": "Tài khoản của bạn đã được đồng ý.",
"welcome_to": "Xin chào ",
"start_posting": "Bắt đầu đăng bài.",
// Translation needed (found during migration to tap:i18n)
"please_fill_in_a_title": "xin nhập tiêu đề",
"seconds_before_posting_again": " một vài giây để đăng lại",
"upvoted": "Thích",
"posted_date": "Ngày đăng",
"posted_time": "Giờ đăng",
"posted_date": "Ngày đăng",
"posted_time": "Giờ đăng",
"profile": "Hồ sơ",
"sign_out": "Đăng xuất",
"you_ve_been_signed_out": "Bạn đã đăng xuất, hẹn sớm gặp lại",
"invitedcount": "đếmMoi",
"invites": "Mời",
"invited": "Được mời?",
"admin": "Admin",
"actions": "Actions",
"invites_left": "invites left",
"id": "ID",
"name": "Tên:",
"bio": "Bio:",
"github": "GitHub",
"site": "website",
"upvoted_posts": "Thích bài",
"downvoted_posts": "Không thích bài",
"mark_as_read": "Đã đọc",
//Common
"pending": "Pending",
"loading": "Tải...",
"submit": "Gửi",
"you_must_be_logged_in": "Bạn phải đăng nhập.",
"are_you_sure": "Bạn có chắn?",
"please_log_in_first": "Xin đăng nhập trước.",
"please_log_in_to_comment": "Đăng nhập để bình luận",
"sign_in_sign_up_with_twitter": "Đăng ký/Đăng nhập với Twitter",
"load_more": "Xem thêm",
"most_popular_posts": "Những bài được xem nhiều nhất",
"newest_posts": "Những bài mới nhất.",
"highest_ranked_posts_ever": "Những bài được thích nhất.",
"the_profile_of": "Hồ sơ của",
"posts_awaiting_moderation": "Bài đang đợi để sửa",
"future_scheduled_posts": "Bài đăng theo lịch",
"users_dashboard": "Bảng người dùng.",
"telescope_settings_panel": "Bản thiết lập Telescope.",
"various_utilities": "Một số tiện ích."
}

View file

@ -65,7 +65,7 @@ analyticsInit = _.once(function() {
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
window.ga('create', googleAnalyticsId);
ga('create', googleAnalyticsId, 'auto');
}

View file

@ -2,6 +2,12 @@
// AccountsTemplates configuration
//////////////////////////////////
if (Meteor.isServer) {
Meteor.startup(function () {
Accounts.emailTemplates.siteName = getSetting('title');
Accounts.emailTemplates.from = getSetting('defaultEmail');
});
}
var removeSpaces = function(value){
return value.replace(" ", "");
@ -14,7 +20,7 @@ AccountsTemplates.addField({
displayName: 'username',
required: true,
minLength: 3,
errStr: 'error.minChar',
errStr: 'error.minChar'
});
AccountsTemplates.removeField('email');
@ -46,7 +52,9 @@ AccountsTemplates.addField({
//Routes
AccountsTemplates.configureRoute('signIn');
AccountsTemplates.configureRoute('signUp');
AccountsTemplates.configureRoute('signUp', {
path: '/register'
});
AccountsTemplates.configureRoute('forgotPwd');
AccountsTemplates.configureRoute('resetPwd');
//AccountsTemplates.configureRoute('changePwd');

View file

@ -3,8 +3,15 @@ cl = function(something){
};
getCurrentTemplate = function() {
return Router.current().lookupTemplate();
var template = Router.current().lookupTemplate();
// on postsDaily route, template is a function
if (typeof template === "function") {
return template();
} else {
return template;
}
};
t=function(message){
var d=new Date();
console.log("### "+message+" rendered at "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds());
@ -48,11 +55,12 @@ goTo = function(url){
// and shouldn't care about the siteUrl.
getRouteUrl = function (routeName, params, options) {
options = options || {};
options.host = getSiteUrl();
return Router.routes[routeName].url(
var route = Router.url(
routeName,
params || {},
options
);
return route;
};
getSignupUrl = function(){

View file

@ -1,8 +1,7 @@
// getPostsParameters gives an object containing the appropriate find and options arguments for the subscriptions's Posts.find()
getPostsParameters = function (terms, user) {
getPostsParameters = function (terms) {
var hasAdminRights = typeof user !== 'undefined' && isAdmin(user);
var maxLimit = 200;
// console.log(terms)
@ -23,12 +22,12 @@ getPostsParameters = function (terms, user) {
// extend sort to sort posts by _id to break ties
deepExtend(true, parameters, {options: {sort: {_id: -1}}});
// if there is a limit, add it too (note: limit=0 means "no limit")
// if a limit was provided with the terms, add it too (note: limit=0 means "no limit")
if (typeof terms.limit !== 'undefined')
_.extend(parameters.options, {limit: parseInt(terms.limit)});
// limit to "maxLimit" posts at most for non-admin users
if(!hasAdminRights && (parameters.options.limit == 0 || parameters.options.limit > maxLimit || !parameters.options.limit)) {
// limit to "maxLimit" posts at most when limit is undefined, equal to 0, or superior to maxLimit
if(!parameters.options.limit || parameters.options.limit == 0 || parameters.options.limit > maxLimit) {
parameters.options.limit = maxLimit;
}

View file

@ -21,7 +21,7 @@ privacyOptions = { // true means exposed
'votes.downvotedComments': true,
'votes.downvotedPosts': true,
'votes.upvotedComments': true,
'votes.upvotedPosts': true,
'votes.upvotedPosts': true
};
// minimum required properties to display avatars
@ -37,4 +37,4 @@ avatarOptions = {
'services.facebook.id': true,
'services.twitter.screenName': true,
'services.github.screenName': true, // Github is not really used, but there are some mentions to it in the code
}
}

View file

@ -3,8 +3,8 @@
CommentPageController = RouteController.extend({
waitOn: function() {
return [
coreSubscriptions.subscribe('singleComment', this.params._id),
coreSubscriptions.subscribe('commentUser', this.params._id),
coreSubscriptions.subscribe('singleCommentAndChildren', this.params._id),
coreSubscriptions.subscribe('commentUsers', this.params._id),
coreSubscriptions.subscribe('commentPost', this.params._id)
];
},

View file

@ -4,7 +4,7 @@
Router._filters = {
isReady: function() {
isReady: function () {
if (!this.ready()) {
// console.log('not ready')
this.render(getTemplate('loading'));
@ -27,7 +27,7 @@ Router._filters = {
},
/*
isLoggedIn: function() {
isLoggedIn: function () {
if (!(Meteor.loggingIn() || Meteor.user())) {
throwError(i18n.t('please_sign_in_first'));
var current = getCurrentRoute();
@ -40,9 +40,8 @@ Router._filters = {
}
},
*/
isLoggedIn: AccountsTemplates.ensureSignedIn,
isLoggedOut: function() {
isLoggedOut: function () {
if(Meteor.user()){
this.render('already_logged_in');
} else {
@ -50,7 +49,7 @@ Router._filters = {
}
},
isAdmin: function() {
isAdmin: function () {
if(!this.ready()) return;
if(!isAdmin()){
this.render(getTemplate('no_rights'));
@ -59,7 +58,7 @@ Router._filters = {
}
},
canView: function() {
canView: function () {
if(!this.ready() || Meteor.loggingIn()){
this.render(getTemplate('loading'));
} else if (!can.view()) {
@ -69,6 +68,24 @@ Router._filters = {
}
},
canViewPendingPosts: function () {
var post = this.data();
if (!!post && post.status == STATUS_PENDING && !can.viewPendingPosts()) {
this.render(getTemplate('no_rights'));
} else {
this.next();
}
},
canViewRejectedPosts: function () {
var post = this.data();
if (!!post && post.status == STATUS_REJECTED && !can.viewRejectedPosts()) {
this.render(getTemplate('no_rights'));
} else {
this.next();
}
},
canPost: function () {
if(!this.ready() || Meteor.loggingIn()){
this.render(getTemplate('loading'));
@ -80,7 +97,7 @@ Router._filters = {
}
},
canEditPost: function() {
canEditPost: function () {
if(!this.ready()) return;
// Already subscribed to this post by route({waitOn: ...})
var post = Posts.findOne(this.params._id);
@ -92,7 +109,7 @@ Router._filters = {
}
},
canEditComment: function() {
canEditComment: function () {
if(!this.ready()) return;
// Already subscribed to this comment by CommentPageController
var comment = Comments.findOne(this.params._id);
@ -104,7 +121,7 @@ Router._filters = {
}
},
hasCompletedProfile: function() {
hasCompletedProfile: function () {
if(!this.ready()) return;
var user = Meteor.user();
if (user && ! userProfileComplete(user)){
@ -114,7 +131,7 @@ Router._filters = {
}
},
setTitle: function() {
setTitle: function () {
// if getTitle is set, use it. Otherwise default to site title.
var title = (typeof this.getTitle === 'function') ? this.getTitle() : getSetting("title", "Telescope");
document.title = title;
@ -136,7 +153,7 @@ Meteor.startup( function (){
// Load Hooks
Router.onRun( function () {
Router.onBeforeAction( function () {
Session.set('categorySlug', null);
// if we're not on the search page itself, clear search query and field
@ -149,19 +166,28 @@ Meteor.startup( function (){
});
// onRun Hooks
// note: this has to run in an onRun hook, because onBeforeAction hooks can get called multiple times
// per route, which would erase the message before the user has actually seen it
// TODO: find a way to make this work even with HCRs.
Router.onRun(filters.clearSeenMessages);
// Before Hooks
Router.onBeforeAction(filters.isReady);
Router.onBeforeAction(filters.clearSeenMessages);
Router.onBeforeAction(filters.canView, {except: ['atSignIn', 'atSignUp', 'atForgotPwd', 'atResetPwd', 'signOut']});
Router.onBeforeAction(filters.canViewPendingPosts, {only: ['post_page']});
Router.onBeforeAction(filters.canViewRejectedPosts, {only: ['post_page']});
Router.onBeforeAction(filters.hasCompletedProfile);
Router.onBeforeAction(filters.isLoggedIn, {only: ['post_submit', 'post_edit', 'comment_reply', 'comment_edit']});
Router.onBeforeAction(filters.isLoggedOut, {only: []});
Router.onBeforeAction(filters.canPost, {only: ['posts_pending', 'post_submit']});
Router.onBeforeAction(filters.canEditPost, {only: ['post_edit']});
Router.onBeforeAction(filters.canEditComment, {only: ['comment_edit']});
Router.onBeforeAction(filters.isAdmin, {only: ['posts_pending', 'all-users', 'settings', 'toolbox', 'logs']});
Router.plugin('ensureSignedIn', {only: ['post_submit', 'post_edit', 'comment_edit']});
// After Hooks
// Router.onAfterAction(filters.resetScroll, {except:['posts_top', 'posts_new', 'posts_best', 'posts_pending', 'posts_category', 'all-users']});

View file

@ -1,13 +1,7 @@
var getDefaultViewController = function () {
var defaultView = getSetting('defaultView', 'top');
defaultView = defaultView.charAt(0).toUpperCase() + defaultView.slice(1);
return eval("Posts"+defaultView+"Controller");
};
// Controller for all posts lists
PostsListController = RouteController.extend({
template: getTemplate('posts_list'),
subscriptions: function () {
@ -28,22 +22,17 @@ PostsListController = RouteController.extend({
},
data: function () {
this._terms = {
view: this.view,
limit: this.params.limit || getSetting('postsPerPage', 10),
category: this.params.slug
};
if(Meteor.isClient) {
this._terms.query = Session.get("searchQuery");
}
var parameters = getPostsParameters(this._terms),
postCount = Posts.find(parameters.find, parameters.options).count();
postsCount = Posts.find(parameters.find, parameters.options).count();
parameters.find.createdAt = { $lte: Session.get('listPopulatedAt') };
var posts = Posts.find(parameters.find, parameters.options);
// Incoming posts
parameters.find.createdAt = { $gt: Session.get('listPopulatedAt') };
var postsIncoming = Posts.find(parameters.find, parameters.options);
@ -52,9 +41,18 @@ PostsListController = RouteController.extend({
return {
incoming: postsIncoming,
postsList: posts,
postCount: postCount,
ready: this.postsListSub.ready
postsCursor: posts,
postsCount: postsCount,
postsReady: this.postsListSub.ready(),
hasMorePosts: this._terms.limit == postsCount,
loadMoreHandler: function () {
var count = parseInt(Session.get('postsLimit')) + parseInt(getSetting('postsPerPage', 10));
var categorySegment = Session.get('categorySlug') ? Session.get('categorySlug') + '/' : '';
// TODO: use Router.path here?
Router.go('/' + Session.get('view') + '/' + categorySegment + count);
}
};
},
@ -77,8 +75,28 @@ PostsListController = RouteController.extend({
fastRender: true
});
var getDefaultViewController = function () {
var defaultView = getSetting('defaultView', 'top');
defaultView = defaultView.charAt(0).toUpperCase() + defaultView.slice(1);
return eval("Posts"+defaultView+"Controller");
};
// wrap in startup block to make sure Settings collection is defined
Meteor.startup(function () {
PostsDefaultController = getDefaultViewController().extend({
getTitle: function () {
var title = getSetting('title', 'Telescope');
var tagline = getSetting('tagline');
var fullTitle = !!tagline ? title + ' ' + tagline : title ;
return fullTitle;
}
});
});
PostsTopController = PostsListController.extend({
view: 'top'
view: 'top',
});
PostsNewController = PostsListController.extend({
@ -100,7 +118,7 @@ PostsScheduledController = PostsListController.extend({
// Controller for post pages
PostPageController = RouteController.extend({
template: getTemplate('post_page'),
waitOn: function() {
@ -133,6 +151,7 @@ PostPageController = RouteController.extend({
onRun: function() {
var sessionId = Meteor.default_connection && Meteor.default_connection._lastSessionId ? Meteor.default_connection._lastSessionId : null;
Meteor.call('increasePostViews', this.params._id, sessionId);
this.next();
},
data: function() {
@ -145,7 +164,7 @@ Meteor.startup(function () {
Router.route('/', {
name: 'posts_default',
controller: getDefaultViewController()
controller: PostsDefaultController
});
Router.route('/top/:limit?', {

View file

@ -1,3 +1,24 @@
var increasePostClicks = function(postId, ip){
var clickEvent = {
name: 'click',
properties: {
postId: postId,
ip: ip
}
}
// make sure this IP hasn't previously clicked on this post
var existingClickEvent = Events.findOne({name: 'click', 'properties.postId': postId, 'properties.ip': ip});
if(!existingClickEvent){
logEvent(clickEvent);
Posts.update(postId, { $inc: { clickCount: 1 }});
}
}
Meteor.startup(function (){
// Link Out
@ -10,8 +31,8 @@ Meteor.startup(function (){
if(query.url){ // for some reason, query.url doesn't need to be decoded
var post = Posts.findOne({url: query.url});
if (post) {
var sessionId = Meteor.default_connection && Meteor.default_connection._lastSessionId ? Meteor.default_connection._lastSessionId : null;
Meteor.call('increasePostClicks', post._id, sessionId);
var ip = this.request.connection.remoteAddress;
increasePostClicks(post._id, ip);
this.response.writeHead(302, {'Location': query.url});
} else {
// don't redirect if we can't find a post for that link

View file

@ -46,7 +46,8 @@ UserEditController = RouteController.extend({
]
},
data: function() {
var user = Meteor.users.findOne({slug: this.params.slug});
// if there is no slug, default to current user
var user = !!this.params.slug ? Meteor.users.findOne({slug: this.params.slug}) : Meteor.user();
return {
user: user
};
@ -95,28 +96,16 @@ Meteor.startup(function () {
}
});
Router.route('/account', {
name: 'userAccountShortcut',
template: getTemplate('user_edit'),
controller: UserEditController
});
// All Users
Router.route('/all-users/:limit?', {
name: 'all-users',
template: getTemplate('users'),
waitOn: function() {
var limit = parseInt(this.params.limit) || 20;
return coreSubscriptions.subscribe('allUsers', this.params.filterBy, this.params.sortBy, limit);
},
data: function() {
var limit = parseInt(this.params.limit) || 20,
parameters = getUsersParameters(this.params.query.filterBy, this.params.query.sortBy, limit),
filterBy = (typeof this.params.query.filterBy === 'string') ? this.params.query.filterBy : 'all',
sortBy = (typeof this.params.query.sortBy === 'string') ? this.params.query.sortBy : 'createdAt';
Session.set('usersLimit', limit);
return {
users: Meteor.users.find(parameters.find, parameters.options),
filterBy: filterBy,
sortBy: sortBy
};
},
fastRender: true
Router.route('/all-users', {
template: getTemplate('users')
});
// Unsubscribe (from notifications)

View file

@ -141,8 +141,6 @@ setUserSetting = function (setting, value, userArgument) {
var field = {};
field['profile.'+setting] = value;
var options = {$set: field};
// console.log(find);
// console.log(options);
var result = Meteor.users.update(find, options, {validate: false});
};

View file

@ -1 +1 @@
telescopeVersion = "0.12.0";
telescopeVersion = "0.14.3";

View file

@ -1,9 +1,7 @@
// returns how much "power" a user's votes have
var getVotePower = function (user) {
// return isAdmin(user) ? 5 : 1;
return 1; // for now, leave everybody at 1 including admins; 5 is too unbalanced
};
// getVotePower returns how much "power" a user's votes have
// It is can be set in a package, by setting getVotePower to a Number or Function then re-exporting
// The default is found in base.js in the base package, and returns 1.
var modifyKarma = function (userId, karma) {
Meteor.users.update({_id: userId}, {$inc: {karma: karma}});
@ -36,14 +34,24 @@ var removeVote = function (userId, itemId, collection, upOrDown) {
};
upvoteItem = function (collection, item, user) {
var user = typeof user === "undefined" ? Meteor.user() : user,
votePower = getVotePower(user),
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
user = typeof user === "undefined" ? Meteor.user() : user;
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
// make sure user has rights to upvote first
if (!user || !can.upvote(user, collection, true) || hasUpvotedItem(item, user))
if (!user || !can.vote(user, true) || hasUpvotedItem(item, user))
return false;
// ------------------------------ Callbacks ------------------------------ //
// run all upvote callbacks on item successively
item = upvoteMethodCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, item);
// ----------------------------------------------------------------------- //
votePower = getVotePower(user);
// in case user is upvoting a previously downvoted item, cancel downvote first
cancelDownvote(collection, item, user);
@ -55,6 +63,7 @@ upvoteItem = function (collection, item, user) {
});
if (result > 0) {
// Add item to list of upvoted items
var vote = {
itemId: item._id,
@ -81,20 +90,43 @@ upvoteItem = function (collection, item, user) {
});
}
}
// --------------------- Server-Side Async Callbacks --------------------- //
if (Meteor.isServer) {
Meteor.defer(function () { // use defer to avoid holding up client
// run all post submit server callbacks on post object successively
result = upvoteCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, item);
});
}
// ----------------------------------------------------------------------- //
}
// console.log(collection.findOne(item._id));
return true;
};
downvoteItem = function (collection, item, user) {
var user = typeof user === "undefined" ? Meteor.user() : user,
votePower = getVotePower(user),
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
user = typeof user === "undefined" ? Meteor.user() : user;
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
// make sure user has rights to downvote first
if (!user || !can.downvote(user, collection, true) || hasDownvotedItem(item, user))
if (!user || !can.vote(user, true) || hasDownvotedItem(item, user))
return false;
// ------------------------------ Callbacks ------------------------------ //
// run all downvote callbacks on item successively
item = downvoteMethodCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, item);
// ----------------------------------------------------------------------- //
votePower = getVotePower(user);
// in case user is downvoting a previously upvoted item, cancel upvote first
cancelUpvote(collection, item, user);
@ -121,20 +153,42 @@ downvoteItem = function (collection, item, user) {
// if the item is being upvoted by its own author, don't give karma
if (item.userId != user._id)
modifyKarma(item.userId, votePower);
// --------------------- Server-Side Async Callbacks --------------------- //
if (Meteor.isServer) {
Meteor.defer(function () { // use defer to avoid holding up client
// run all post submit server callbacks on post object successively
result = downvoteCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, result);
});
}
// ----------------------------------------------------------------------- //
}
// console.log(collection.findOne(item._id));
return true;
};
cancelUpvote = function (collection, item, user) {
var user = typeof user === "undefined" ? Meteor.user() : user,
votePower = getVotePower(user),
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
user = typeof user === "undefined" ? Meteor.user() : user;
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
// if user isn't among the upvoters, abort
if (!hasUpvotedItem(item, user))
return false;
// ------------------------------ Callbacks ------------------------------ //
// run all cancel upvote callbacks on item successively
item = cancelUpvoteMethodCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, item);
// ----------------------------------------------------------------------- //
votePower = getVotePower(user);
// Votes & Score
var result = collection.update({_id: item && item._id, upvoters: user._id},{
$pull: {upvoters: user._id},
@ -153,20 +207,43 @@ cancelUpvote = function (collection, item, user) {
// if the item is being upvoted by its own author, don't give karma
if (item.userId != user._id)
modifyKarma(item.userId, votePower);
// --------------------- Server-Side Async Callbacks --------------------- //
if (Meteor.isServer) {
Meteor.defer(function () { // use defer to avoid holding up client
// run all post submit server callbacks on post object successively
result = cancelUpvoteCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, result);
});
}
// ----------------------------------------------------------------------- //
}
// console.log(collection.findOne(item._id));
return true;
};
cancelDownvote = function (collection, item, user) {
var user = typeof user === "undefined" ? Meteor.user() : user,
votePower = getVotePower(user),
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
user = typeof user === "undefined" ? Meteor.user() : user;
collectionName = collection._name.slice(0,1).toUpperCase()+collection._name.slice(1);
// if user isn't among the downvoters, abort
if (!hasDownvotedItem(item, user))
return false;
// ------------------------------ Callbacks ------------------------------ //
// run all cancel downvote callbacks on item successively
item = cancelDownvoteMethodCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, item);
// ----------------------------------------------------------------------- //
votePower = getVotePower(user);
// Votes & Score
var result = collection.update({_id: item && item._id, downvoters: user._id},{
$pull: {downvoters: user._id},
@ -185,6 +262,19 @@ cancelDownvote = function (collection, item, user) {
// if the item is being upvoted by its own author, don't give karma
if (item.userId != user._id)
modifyKarma(item.userId, votePower);
// --------------------- Server-Side Async Callbacks --------------------- //
if (Meteor.isServer) {
Meteor.defer(function () { // use defer to avoid holding up client
// run all post submit server callbacks on post object successively
result = cancelDownvoteCallbacks.reduce(function(result, currentFunction) {
return currentFunction(collection, result, user);
}, result);
});
}
// ----------------------------------------------------------------------- //
}
// console.log(collection.findOne(item._id));
return true;

View file

@ -48,7 +48,7 @@ primaryNav = [
secondaryNav = [
{
template: 'userMenu',
template: 'userMenu',
order: 10
},
{
@ -185,14 +185,20 @@ heroModules = [];
footerModules = [];
threadModules = [];
postModules = [
{
template: 'postUpvote',
template: 'postRank',
order: 1
},
{
template: 'postUpvote',
order: 10
},
{
template: 'postContent',
order: 5
order: 20
},
{
template: 'postAvatars',
@ -241,14 +247,20 @@ postMeta = [
]
// ------------------------------ Callbacks ------------------------------ //
postClassCallbacks = [];
postSubmitClientCallbacks = [];
postSubmitMethodCallbacks = [];
postAfterSubmitMethodCallbacks = []; // runs on server only in a timeout
postEditClientCallbacks = []; // loops over post object
postEditClientCallbacks = []; // loops over modifier object
postEditMethodCallbacks = []; // loops over modifier (i.e. "{$set: {foo: bar}}") object
postAfterEditMethodCallbacks = []; // loops over modifier object
postApproveCallbacks = [];
commentClassCallbacks = [];
commentSubmitRenderedCallbacks = [];
commentSubmitClientCallbacks = [];
commentSubmitMethodCallbacks = [];
@ -264,6 +276,15 @@ userEditClientCallbacks = [];
userCreatedCallbacks = [];
userProfileCompleteChecks = [];
upvoteCallbacks = [];
downvoteCallbacks = [];
cancelUpvoteCallbacks = [];
cancelDownvoteCallbacks = [];
upvoteMethodCallbacks = [];
downvoteMethodCallbacks = [];
cancelUpvoteMethodCallbacks = [];
cancelDownvoteMethodCallbacks = [];
// ------------------------------------- User Profiles -------------------------------- //
userProfileDisplay = [
@ -321,4 +342,13 @@ themeSettings = {
// ------------------------------ Subscriptions ------------------------------ //
// array containing subscriptions to be preloaded
preloadSubscriptions = [];
preloadSubscriptions = [];
// ------------------------------- Vote Power -------------------------------- //
// The equation to determine voting power
// Default to returning 1 for everybody
getVotePower = function (user) {
return 1;
};

View file

@ -27,22 +27,29 @@ Package.onUse(function (api) {
'viewParameters',
'footerModules',
'heroModules',
'threadModules',
'postModules',
'postThumbnail',
'postHeading',
'postMeta',
'modulePositions',
'postClassCallbacks',
'postSubmitRenderedCallbacks',
'postSubmitClientCallbacks',
'postSubmitMethodCallbacks',
'postAfterSubmitMethodCallbacks',
'postApproveCallbacks',
'postEditRenderedCallbacks',
'postEditClientCallbacks',
'postEditMethodCallbacks',
'postAfterEditMethodCallbacks',
'commentClassCallbacks',
'commentSubmitRenderedCallbacks',
'commentSubmitClientCallbacks',
'commentSubmitMethodCallbacks',
@ -53,6 +60,15 @@ Package.onUse(function (api) {
'commentEditMethodCallbacks',
'commentAfterEditMethodCallbacks',
'upvoteCallbacks',
'downvoteCallbacks',
'cancelUpvoteCallbacks',
'cancelDownvoteCallbacks',
'upvoteMethodCallbacks',
'downvoteMethodCallbacks',
'cancelUpvoteMethodCallbacks',
'cancelDownvoteMethodCallbacks',
'userEditRenderedCallbacks',
'userEditClientCallbacks',
'userProfileCompleteChecks',
@ -63,6 +79,8 @@ Package.onUse(function (api) {
'getTemplate',
'templates',
'themeSettings'
'themeSettings',
'getVotePower'
]);
});

View file

@ -0,0 +1,5 @@
{
"translation_key": "chave de tradução",
"customViewLink": "Link de Visão Personalizado",
"customAdminLink": "Link Admin Personalizado"
}

View file

@ -62,7 +62,7 @@ Package.onUse(function (api) {
'lib/client/templates/custom_template.html',
'lib/client/templates/custom_template.js',
'lib/client/templates/customPostTitle.html',
'lib/client/scss/custom.scss'
'lib/client/stylesheets/custom.scss'
], ['client']);
// server

View file

@ -1,4 +1,5 @@
{
"daily": "Daily",
"day_by_day_view": "The most popular posts of each day."
"day_by_day_view": "The most popular posts of each day.",
"load_next_days": "Load Next Days"
}

View file

@ -0,0 +1,5 @@
{
"daily": "Diario",
"day_by_day_view": "Los post mas populares de cada día.",
"load_next_days": "Cargar días siguientes"
}

View file

@ -0,0 +1,5 @@
{
"daily": "Dziennie",
"day_by_day_view": "Najpopularniejsze posty każdego dnia.",
"load_next_days": "Wczytaj kolejne dni"
}

View file

@ -0,0 +1,5 @@
{
"daily": "Diário",
"day_by_day_view": "As postagens mais populares de cada dia.",
"load_next_days": "Carregar Próximos Dias"
}

View file

@ -1,11 +0,0 @@
.posts-day-heading{
font-size: 16px;
color: rgba(0,0,0,0.5);
margin: 20px 0 10px 0;
}
.empty-day-notice{
padding: 15px;
}
.posts-day .post:last-child{
border-radius: 0 0 3px 3px;
}

View file

@ -0,0 +1,21 @@
.posts-day-heading{
font-size: 16px;
color: rgba(0,0,0,0.5);
margin: 0 0 10px 0;
}
.empty-day-notice{
padding: 15px;
}
.posts-day .post:last-child{
border-radius: 0 0 3px 3px;
}
.load-more-days-button{
display: block;
background: none;
width:100%;
text-align:center;
font-size:18px;
padding: 0px;
text-align: center;
margin: 20px 0;
}

View file

@ -1,27 +1,13 @@
<template name="postsDaily">
{{#if postsLoaded}}
<div class="grid">
{{#each days}}
{{> UI.dynamic template=before_day}}
<h2 class="posts-day-heading">{{formatDate date "dddd, MMMM Do YYYY"}}</h2>
<div class="posts-wrapper posts-day posts list grid-module">
{{#if posts}}
{{#each posts}}
{{> UI.dynamic template=before_post_item}}
{{> UI.dynamic template=post_item}}
{{> UI.dynamic template=after_post_item}}
{{/each}}
{{else}}
<p class="empty-day-notice">Sorry, no posts for that day.</p>
{{/if}}
</div>
{{> UI.dynamic template=after_day}}
{{/each}}
<a class="more-button grid-module" href="{{loadMoreUrl}}"><span>{{_ "load_more"}}</span></a>
</div>
{{else}}
<div class="grid loading-module">
{{>spinner}}
</div>
{{/if}}
<div class="grid">
{{#each days}}
{{> Template.dynamic template=before_day}}
<h2 class="posts-day-heading">{{formatDate date "dddd, MMMM Do YYYY"}}</h2>
{{> Template.dynamic template=singleDay data=context}}
{{> Template.dynamic template=after_day}}
{{/each}}
<a class="load-more-days-button grid-module" href="{{loadMoreDaysUrl}}"><span>{{_ "load_next_days"}}</span></a>
</div>
</template>

View file

@ -1,28 +1,6 @@
var daysPerPage = 5;
var getPosts = function (date) {
var terms = {
view: 'digest',
after: moment(date).startOf('day').toDate(),
before: moment(date).endOf('day').toDate()
};
var parameters = getPostsParameters(terms);
var posts = Posts.find(parameters.find, parameters.options).map(function (post, index, cursor) {
post.rank = index;
return post;
});
return posts;
}
Meteor.startup(function () {
Template[getTemplate('postsDaily')].helpers({
postsLoaded: function () {
return !!Session.get('postsLoaded');
},
post_item: function () {
return getTemplate('post_item');
},
days: function () {
var daysArray = [];
// var days = this.days;
@ -38,13 +16,18 @@ Meteor.startup(function () {
before_day: function () {
return getTemplate('beforeDay');
},
singleDay: function () {
return getTemplate('singleDay');
},
context: function () {
var context = this;
context.showDateNav = false;
return context;
},
after_day: function () {
return getTemplate('afterDay');
},
posts: function () {
return getPosts(this.date);
},
loadMoreUrl: function () {
loadMoreDaysUrl: function () {
var count = parseInt(Session.get('postsDays')) + daysPerPage;
return '/daily/' + count;
}

Some files were not shown because too many files have changed in this diff Show more