Compare commits

...

251 Commits

Author SHA1 Message Date
Tim Abbott
a6a5636a32 Release Zulip server 1.8.0. 2018-04-17 16:59:07 -07:00
Tim Abbott
9f844ff681 tornado: Fix logging of tornado activity level.
This logging was apparently broken when sorting imports; it's a fairly
unique thing in our codebase that this would be a problem.  Prevent
future regressions by adding this exception explicitly to the isort
configuration.
2018-04-17 15:59:01 -07:00
Tim Abbott
d340c8d46d docs: Fix broken link in /for/companies.
I guess these pages are not covered by tools/test-documentation.
2018-04-17 13:36:35 -07:00
Tim Abbott
60fe92ff13 docs: Make some small tweaks to the changelog. 2018-04-17 13:36:35 -07:00
Tim Abbott
7e187676c6 docs: Update changelog discussion of uploads auth and trusty. 2018-04-17 13:19:34 -07:00
Tim Abbott
c1af2d805c i18n: Update translations. 2018-04-17 12:41:06 -07:00
Aditya Bansal
4898fe7ebc uploads: Change Content-Security-Policy to fix issue with pdf's.
Our recent addition of Content-Security-Policy to the file uploads
backend broke in-browser previews of PDFs.

The content-types change in the last commit fixed loading PDFs for
most users; but the result was ugly, because e.g. Chrome would put the
PDF previewer into a frame (so there were 2 left scrollbars).

There were two changes needed to fix this:
* Loading the style to use the plugin.  We corrected this by adding
  `style-src 'self' 'unsafe-inline';`
* Loading the plugin.  Our CSP blocked loading the PDf viewer plugin.
  To correct this, we add object-src 'self', and then limit the
  plugin-type to just the one for application/pdf.

We verified this new CSP using https://csp-evaluator.withgoogle.com/
in addition to manual testing.
2018-04-17 12:23:24 -07:00
Tim Abbott
568a12e254 nginx: Add PDF files to the content-types list.
Previously, user-uploaded PDF files were not properly rendered by
browsers with the local uploads backend, because we weren't setting
the correct content-type.
2018-04-17 11:50:10 -07:00
Cynthia Lin
ad6fbbed62 night mode: Remove white borders in collapsible sidebars. 2018-04-17 11:18:09 -07:00
Cynthia Lin
45293a18c6 right-sidebar: Align group PM unread counts with user PM unread counts. 2018-04-17 11:06:33 -07:00
Cynthia Lin
b4c977fc6b sidebars: Fix regressions in input fields.
Standardizes their width and margins, while moving an unrelated
selector to a different file.
2018-04-17 11:06:33 -07:00
Vishnu Ks
cc93ac34a8 coverage: Add coverage to estimate_recent_messages.
With this message.py is fully covered and can be
removed from not_yet_fully_covered in test-backend.
2018-04-17 11:01:20 -07:00
Priyank
26d8d98319 frontend_tests: Add prefer-const rule.
This rule checks for use of const wherever needed, currently does
nothing since we don't use `let`, instead we use `var`. This rule
can be used to use refactor a file to use const easily by replaceing
var with let using a editor and then by running
`./node_modules/.bin/eslint frontend_tests --fix --cache`. And then revert
those `let`'s back to `var`.
2018-04-17 12:56:25 -04:00
Priyank
6faa6f96e9 node_test: Convert function to arrow function where needed. 2018-04-17 12:56:25 -04:00
Priyank
7490932e1b node_tests: Use const for constants. 2018-04-17 12:56:25 -04:00
Eeshan Garg
4fbdfef63b webhooks/stripe: Update docs to conform to new style guide. 2018-04-17 09:07:27 -07:00
Greg Price
dace7cacc8 docs changelog: Mention there are security fixes since 1.7.
Can't hurt to make this clear right in the 1.8 notes.
2018-04-16 18:37:55 -07:00
Greg Price
8630eb43b3 docs: Sort changelog entries for 1.8 into categories.
These aren't perfect -- in particular "core chat experience" can
probably be broken up -- but I think they help in making a quick skim
work for getting some sense of what the changes are.

This change just reorders and adds headings, with virtually no wording
changes.
2018-04-16 18:37:38 -07:00
Puneeth Chaganti
26dfa3266b ci: Make cache keys depend on checksums of dependency manifests
Caches in Circle are immutable -- even if a path in a cache changes in builds
after the cache was created, the cache is not updated if it already exists. This
was making the zulip-venv-cache and zulip-npm-cache directories useless on
Circle.

This commit changes the cache keys to depend on the checksums of the dependency
manifests (requirements/{dev,thumbor}.txt, package.json and yarn.lock). This
would ensure that the caches are updated when the environments change. It may
result in the occasional build being "uncached" -- when a dependency manifest
changes -- but builds without such changes will be much faster, and such builds
are a majority.
2018-04-16 16:49:41 -07:00
Eeshan Garg
da4ac38e37 css: Stop rendering code blocks as inline-blocks in webhook docs.
Previously, a code block with a small width would be displayed
inline with the previous paragraph's text.

To fix this, now every p inside an li element except the first is
a block instead of an inline-block. However, this only applies to
li elements for integration instructions.

This makes sense intuitively because if there are multiple p's
in a list element, not all of those should be inline-blocks. The
first one should be because it needs to be inline with the list
number. The rest should be treated (and displayed) as separate
paragraphs.

Another thing to keep in mind is that the way Markdown code
blocks get converted to HTML is such that every code block
becomes <p><code></code></p> when converted to HTML.
2018-04-16 16:42:07 -07:00
Eeshan Garg
dde9bb448f webhooks/circleci: Add steps instead of linking to CircleCI docs.
We let Markdown increment the list step numbers, which is more
reliable than keeping track of numbered-steps manually.

Also, instead of linking to the CircleCI docs, we now have full
instructions for how to setup a webhook by modifying the circle.yml
file.
2018-04-16 16:39:23 -07:00
Tim Abbott
310b451dc2 Revert "requirements: Use pypi versions of zulip and zulip_bots."
This reverts commit 6b142b35e6.
2018-04-16 16:39:01 -07:00
Tim Abbott
6b142b35e6 requirements: Use pypi versions of zulip and zulip_bots. 2018-04-16 16:14:43 -07:00
Tim Abbott
5cc70675c6 webhooks: Suppress errors from very old GitLab versions.
Ancient GitLab from several years ago doesn't include the
HTTP_X_GITLAB_EVENT header (and seems to have a different format), so
we should ignore its requests.

Might be good to document the version threshhold, but it's very hard
to tell from Googling what it is.
2018-04-16 16:13:20 -07:00
Tim Abbott
e2f8bc9eac /api: Fix tests for /api homepage. 2018-04-16 16:13:20 -07:00
Eeshan Garg
6782f2b76a pypi: Upgrade to release 0.4.4.
This is the latest release after pip 10 was launched.
2018-04-16 16:04:46 -07:00
Tim Abbott
c224114287 /api: Clean up the API documentation homepage. 2018-04-16 15:54:39 -07:00
Tim Abbott
d09071bbc9 /api: Add an overview doc for the REST API. 2018-04-16 15:51:13 -07:00
Tim Abbott
89704df167 /api: Move list of REST endpoints to a template. 2018-04-16 15:50:53 -07:00
Tim Abbott
ea266f1b80 /api: Expand "common errors" page to more generally cover error handling. 2018-04-16 15:50:52 -07:00
Rhea Parekh
a2070fb7e5 slack importer: Add comment on size information of avatars.
The size information of an avatar is not required during the import.
Check function 'import_uploads_local' and 'import_uploads_s3'
in 'export.py' for this.
2018-04-16 14:44:57 -07:00
Tim Abbott
636390104a css: Fix glitchy white line in recipient headers.
The intent had always been for this to be just a color change; a white
boundary didn't look good for either the day or night theme.
2018-04-16 13:37:21 -07:00
Tim Abbott
f6709cc888 i18n: Update translations from transifex. 2018-04-16 13:25:06 -07:00
Nikhil Kumar Mishra
91412e5843 test_upload: Add test for get_realm_for_filename. 2018-04-16 11:52:44 -07:00
Nikhil Kumar Mishra
c96dc1652e test_upload: Add tests for resize_emoji. 2018-04-16 11:52:44 -07:00
Tim Abbott
0c30a26d81 bulk_create: Remove some long-dead code.
We used to use these in populate_db, but haven't done so in a long
time, and it doesn't seem likely that will change anytime in the
future.
2018-04-16 11:41:42 -07:00
Greg Price
21045d8cf0 prod docs: Call out more the need for a chained cert bundle.
This is kind of easy to gloss over, especially with the framing
as a "format"; surely if things work at all, the file format
must have been right, right?  It's really a bit more substantive
than that; say so and also add a bit more description.
2018-04-16 11:34:23 -07:00
Ben Reeves
fdfbd45208 soft_deactivation: Change < to <= in add_missing_messages.
We should still short-circuit the iteration in
`add_missing_messages` if the unsubscription was the last
thing to happen to the user before unsubscription and
soft deactivation.
2018-04-16 11:28:08 -07:00
Alyssa Wagenmaker
d4e5777296 tests: Test user unsubscribing before soft deactivation.
Brings lib/soft_deactivation.py up to 100% test coverage.

Improves: #7089.
2018-04-16 11:28:08 -07:00
Shubham Dhama
03f95ba993 upload: Rename uploadStarted to drop to match original convention.
We used uploadStarted for drop callback which is kind of confusing
for new contributors as there is a big difference between uploadStarted
and drop like uploadStarted is called for each file in an upload whereas
the drop is called once when the file(s) are uploaded.
2018-04-16 11:16:42 -07:00
Shubham Dhama
0d0f971ae1 upload: Fix stacking of progress bar on canceling the upload.
This fixes stacking of upload progress bar when upload is canceled
and later made another upload.
2018-04-16 23:00:21 +05:30
Tim Abbott
593201a107 css: Cleanup CSS for sidebars.
This fixes a handful of minor issues:

* Non-uniform padding for the right sidebar unread count bubbles.
* Weird vertical positioning of unread counts in the right sidebar due
  to a slightly off line height.
* Missing padding between long stream names and the unread count for the stream.
* Removes a duplicate border-radius command in the left sidebar CSS.
2018-04-16 10:04:37 -07:00
Cynthia Lin
d0f5ae38cc right-sidebar: Properly align unread counts with topic names. 2018-04-16 09:48:04 -07:00
Cynthia Lin
47d50c6b86 left-sidebar: Properly align unread counts with topic names.
Fixes #7492.
2018-04-16 09:48:01 -07:00
Cynthia Lin
7cbc9f40bf compose: Change styling of upload progress bar.
Related to #9095.
2018-04-16 09:46:35 -07:00
Cynthia Lin
983deff5da compose: Align recipient bar icons properly.
Fixes #9094.
2018-04-16 09:45:30 -07:00
Cynthia Lin
02d122bed5 input-pill: Wrap input pills when they overflow pill container.
Fixes #9096.
2018-04-16 09:44:22 -07:00
Rhea Parekh
f6b6aa1e75 slack import: Implement threading as a management command. 2018-04-15 19:53:02 +05:30
Rhea Parekh
7c0c3930a8 slack importer: Thread avatar downloads. 2018-04-15 19:53:01 +05:30
Rhea Parekh
ebc2ee28e9 slack importer: Thread emoji downloads. 2018-04-15 19:52:59 +05:30
Rhea Parekh
8a291d0232 slack importer: Thread attachment downloads.
Use Zulip's run_parallel method to run thread downloads.
2018-04-15 19:51:58 +05:30
Steve Howell
7666e9c7a9 stream settings: Simplify how we select streams tabs.
This commit introduces a helper function called
maybe_select_tab() that goes to the correct tab in the
toggler widget.

It avoids the "lookup" mechanism, which I am hoping to
deprecate, and it handles hypothetical startup issues
by warning instead of crashing.
2018-04-14 11:40:03 -07:00
Steve Howell
9319da8e1d stream settings: Fix bug with Subscribed/All.
Before this commit, this sequence would lead to errors:

        * Open streams page via the gear menu.
        * Go to "All" tab.
        * Leave streams settings.
        * Re-open stream settings via the gear menu.

After doing this, the tab would show "Subscribed" but the list
would be of all messages.

Now we explicitly goto the first tab.

I added a long comment explaining how subs.js contributed
to this bug--in short, we re-build the widget instead of just
re-opening this.

We may also want the toggle component to simply default the
initial tab to the first tab.
2018-04-14 11:40:03 -07:00
Steve Howell
a2354ce699 Prevent traceback with info overlays.
We now make sure our toggler exists before invoking its `goto`
method.  Usually a toggler exists pretty early during app
startup, but _setup_info_overlay is wrapped in i18n.ensure_i18n,
which asynchronously fetches translation data.

This commit also simplifies how we find the toggler, by just
storing it in the module where it gets created and consumed.

Fixes #9085.
2018-04-14 11:40:03 -07:00
Eeshan Garg
6d86c83966 webhooks/solano: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
eec7e17e70 webhook/raygun: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
c51a3dce62 webhooks/pivotal: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
911b9582bd webhooks/opbeat: Update docs to conform to style guide. 2018-04-14 09:38:22 -07:00
Eeshan Garg
3e0eb9530c webhooks: Remove the Facebook integration.
Rishi and I decided that it makes sense to get rid of the Facebook
integration for a few reasons, some of which are:

* The setup process is too complicated on Facebook's end. The users
  will surely have to browse Facebook's huge API reference before even
  having a vague idea of what they want.
* Slack chooses not to have a Facebook integration, but relies on
  Zapier for it. Zaps that integrate with Facebook are much more
  streamlined and the setup process isn't as much of a pain. Zapier's
  Facebook Zaps are much more fine-tuned and there are different Zaps
  for different parts of the FB API, a luxury that would likely span
  2K+ lines of code on our end if we were to implement it from
  scratch. So, I think we should relegate integration with Facebook to
  Zapier as well!
* After thoroughly testing the setup process, we concluded that the
  person who submitted the FB integration didn't really test it
  thoroughly because there were some gaping holes in the docs (missing
  steps, user permissions, etc.).
2018-04-14 09:38:22 -07:00
Tim Abbott
5ddf2614f0 uploads: Add new way of querying for mobile uploads endpoint.
This extends the /user_uploads API endpoint to support passing the
authentication credentials via the URL, not the HTTP_AUTHORIZATION
headers.  This is an important workaround for the fact that React
Native's Webview system doesn't support setting HTTP_AUTHORIZATION;
the app will be responsible for rewriting URLs for uploaded files
directly to add this parameter.
2018-04-13 17:51:45 -07:00
Tim Abbott
012115c9e0 message_events: Fix updating compose fade after topic editing.
If you started composing a message to a topic, and then the topic was
edited, we would update the compose box and message list state, but we
didn't correctly update the fade state after updating your compose box
(and the message list), resulting in the messages being incorrectly
faded.
2018-04-13 16:31:18 -07:00
Tim Abbott
5ae9505fdc message_list: Move set_message_offset into message_list_view.
The refactor in 12509515ae had a subtle
bug, which is that we switched from accessing the message list "this"
(aka the message list being rerendered) to current_msg_list.  This
meant that when the narrowed_msg_list was in view and code needed to
modify home_msg_list, we accessed the wrong `selected_row` to preserve
the scroll position of (namely, the one in current_msg_list, not the
one in home_msg_list).

Fix this, by moving the function to be a property of the
message_list_view object, which makes more sense structurally, anyway.

We may, in the future, want to do a similar migration for more of
message_viewport.js.

Fixes #8854.
2018-04-13 16:31:18 -07:00
Tim Abbott
dbbd3627b5 message_list: Show empty narrow notice after emptying narrow.
If muting, topic editing, or deletion causes a narrow loses its last
message, we should show the empty-narrow notice; similarly, if
un-muting adds the first message, we should hide the notice.

We do this in `rerender()` since that's the common code path for
re-rendering the message list after events that might change this.

This has likely been broken since the very first muting
and topic editing implementations.
2018-04-13 16:31:18 -07:00
Tim Abbott
a8d237b252 message_list: Extract rerender_with_target_scrolltop.
We may find this useful in upcoming edits to the muting code.
2018-04-13 16:31:18 -07:00
Aastha Gupta
0c219a1905 invites ui: fixed rerendering of invites list
Earlier it was re-initialising invites list on every load.
Now, if the list is already initialised, it will reutilise
those resources.
2018-04-13 12:07:42 -07:00
Tarun Kumar
0b62410f5e pm_pill: Achieve 100% node-test coverage for compose_pm_pill.js. 2018-04-13 11:59:57 -07:00
Tarun Kumar
51601fab44 muting: Achieve 100% node-test coverage for settings_muting.js. 2018-04-13 11:59:57 -07:00
Aditya Bansal
113c1a81ea index.html: Refactor 'javascript:' URI to be written as JavaScript block.
This is important because we intent to start using CSP in our main
app which means dropping of any inline event handlers
(onclick="...", onerror="...") and <a href="javascript:...">
links that can be used to run scripts.
2018-04-13 11:10:06 -07:00
Rohitt Vashishtha
7a4788b364 node-tests: Add basic tests for notifications API.
This commit exposes some inner variables of notifications.js to make
them easily testable. The first test added simply checks whether the
showing and closing of notifications works properly, and doesn't yet
verify the main code logic of the notification generation.
2018-04-13 09:13:50 -07:00
Rohitt Vashishtha
703351288a zjquery: Add replaceWith() and selector.location. 2018-04-13 09:13:50 -07:00
Rohitt Vashishtha
2e7f215f44 zjquery: Add option to silently ignore find() errors. 2018-04-13 09:13:50 -07:00
Tim Abbott
db830c4085 bugdown: Replace link to old pre-open-source Zulip trac. 2018-04-13 08:41:44 -07:00
Rohitt Vashishtha
9e7929417d markdown: Increase rendered_content length limit.
This commit increases the rendered_content limit from 2x to 10x of the
original message length.

Earlier, we had placed a limit of MAX_MESSAGE_LENGTH * 2 for the
rendered content (explained in commit
77addc5456).  That limit was based on
the assumption that in most cases, the rendered content wouldn't cause
a large increase in message length. However, quite prominently in
syntax highlighted codeblocks, that wasn't true and this caused the
limit condition to be hit for long messages composed primarily of code
blocks.

Example: The following message would render close to 10x it's original size.

```py
if:
def:
print("x", var)
x = y
```

Because the syntax highlighted logic is extremely compressible, having
rendered_content reach up to 100KB doesn't create a network
performance problem.
2018-04-13 08:39:51 -07:00
Tim Abbott
cc927774af docs: Add an initial document explaining our presence protocol. 2018-04-12 17:04:51 -07:00
novokrest
0d164fab1b message_edit_form: Show default cursor instead of pointer.
Fixes part of #3938.

(This only changes the sections within the message-edit form; the top
and left chrome are still cursor: pointer).
2018-04-12 15:08:38 -07:00
Gooca
c4e2899f99 settings: Pull nested radio-buttons closer together.
Exact spacing tweaked by tabbott.

Fixes #9066.
2018-04-12 14:46:23 -07:00
Tim Abbott
105eed049e install-node: Fix leaking of $HOME.
This fixes a bug where provision was failing since our most recent
upgrade to yarn/nvm/node.

It turns out my original fix was the correct fix, but to the wrong
third-party tool: nvm, not yarn, was the offender.
2018-04-12 14:32:36 -07:00
Steve Howell
f56b4b7ec2 zjquery: Simplify validation for $(...).
We flatten the code a bit by removing a check that type is object,
and we replace it later with a check that type is string.

We also no longer allow document-like objects to be wrapped based
on the location-attribute-is-present hack.  Instead, we want the
tests to just set document to 'document-stub'.
2018-04-12 11:37:01 -07:00
Steve Howell
368b37e198 zjquery: Add a selector attribute to our wrapped elements.
We might as well have this for debugging purposes and to simplify
the code that happens when we double-wrap DOM elements.
2018-04-12 11:37:01 -07:00
Steve Howell
f78947f2ba zjquery: Fix minor typo in comments.
This typo came from s/impl/self/ at some point :)
2018-04-12 11:37:01 -07:00
Steve Howell
174d065b2e zjquery: Add to_$ hook so elements can wrap themselves. 2018-04-12 11:37:01 -07:00
Steve Howell
f505f3de04 zjquery: Support $.fn.foo mechanism.
We can now extend zjquery using the $.fn mechanism.  This isn't
necessarily recommended for test code (since you can just stub
individual objects directly), but some of our real code does this.
2018-04-12 11:37:00 -07:00
Steve Howell
f5e0794d5c zjsquery: Give special treatment to "body" tag.
We don't do things like $('table') much in our code, but $('body')
is in the code.
2018-04-12 11:37:00 -07:00
Steve Howell
3971fae05d node tests: Fix some leaks with $(...). 2018-04-12 11:37:00 -07:00
Tim Abbott
041fd802b7 Revert "yarn: Revert back to v0.27.5."
This reverts commit d4b88e86cc.
2018-04-12 11:37:00 -07:00
Tim Abbott
f6ae57fa70 install-node: Correctly fix yarn installation.
Apparently, new versions of yarn use the HOME environment variable to
figure out where to access their configuration, and sudo apparently
doesn't clear that variable, so install-node was being run with HOME
set to something under /home/vagrant (e.g.).

Fix this by just setting that environment variable correctly.

This replaces 250a036ff8, which
misdiagnosed the issue.
2018-04-12 11:37:00 -07:00
Steve Howell
205bcb8ef9 Fix recently broken reactions tests with s/html/text/.
In the real code, as part of a quick security patch, we started
using text() to set values instead of html().  The tests now
reflect this.
2018-04-12 11:29:08 -07:00
Shubham Dhama
50545a3571 settings: Revert checkmark icon to fontawesome and checkbox-green.
In 7b8da9b we have introduced some other checkmark icons
which aren't necessary as old icons still make sense there.

So removing them as they don't add any extra value.

Fixes: #8995.
2018-04-12 11:06:43 -07:00
Tim Abbott
250a036ff8 install-node: Fix yarn installation.
It appears that some change in yarn's versioning system means that
installing yarn itself ends up chowning its config directory
incorrectly to be owned by root, preventing `yarn install` from
working later.
2018-04-12 10:42:27 -07:00
Tim Abbott
fea65cbb01 Update changelog and versions following Zulip Server 1.7.2. 2018-04-12 10:20:49 -07:00
Priyank
d4b88e86cc yarn: Revert back to v0.27.5.
Revert yarn version back due to some issue with new version that causes
permission issues in ~/.config/yarn directory.

Related discussion: https://chat.zulip.org/#narrow/stream/21-provision-help/topic/EACCES.3A.20permission.20denied.2C.20scandir.20'.2Fhome.2Fvagrant.2F.2Econfig.2Fya
2018-04-12 10:18:59 -07:00
Tim Abbott
feef35bf25 linter: Add checks for sloppy use of .html().
Since jQuery's .html() can be a source of security bugs, we add a new
lint rule that tries to catch common problematic uses.
2018-04-12 09:50:14 -07:00
Tim Abbott
5f0f492205 unread: Clean up variable names around bankruptcy modal.
unread_info was a particularly bad name for the HTML.
2018-04-12 09:48:02 -07:00
Tim Abbott
a6d80969f5 subs: Clean up variable name for rendered subscription count. 2018-04-12 09:48:02 -07:00
Tim Abbott
ab8fb23164 emoji: Clean up variable names for rendered template content. 2018-04-12 09:48:02 -07:00
Tim Abbott
c90fbff703 settings: Clarify variable names for tab template rendering. 2018-04-12 09:48:02 -07:00
Tim Abbott
dbb62ba5cb compose: Clean up variable names for preview logic.
This makes it more clear that the content has already been rendered.
2018-04-12 09:48:02 -07:00
Tim Abbott
f4aea3ec22 invite: Clarify variable names for rendered_email_msg. 2018-04-12 09:48:02 -07:00
Tim Abbott
0db715d222 search_suggestion: Add escaping for email addresses.
This is probably unnecessary, but makes me feel better about every
code path in this file doing proper escaping to avoid XSS issues.
2018-04-12 09:47:01 -07:00
Tim Abbott
65b9d9e0f3 CVE-2018-9990: Fix XSS issue with stream names in topic typeahead.
Zulip's search typeahead had a security bug, where when autocompleting
a specially crafted stream name, and then hitting space, code within
the stream name would be executed.

Zulip was doing HTML escaping correctly in the main code path using
Filter.describe to describe a narrow, but the escaping function was
not called in a few parallel code paths.  We fix this in a way that
should protect all of these code paths, by making Filter.describe
return properly escaped HTML, rather than depending on its callers to
do so.

Thanks to w2w for reporting this issue.
2018-04-12 09:46:54 -07:00
Rohitt Vashishtha
3bdc8bbaa5 CVE-2018-9986: Fix XSS issues with frontend markdown processor.
This fixes a set of XSS issues with Zulip's frontend markdown
processor, which is used in a limited set of contexts, such as local
echo of messages and the drafts feature.

The implementation of several syntax elements, including the <em>
syntax, user and stream mentions, and some others failed to properly
escape the content inside the syntax.

Fix this, and add tests for each corrected code path.

Thanks to w2w for reporting this issue.
2018-04-12 09:46:37 -07:00
Tim Abbott
1207a08b36 CVE-2018-9987: Fix XSS issue with muting notifications.
This fixes an XSS issue with Zulip's muting UI, where if a stream or
topic name contained malicious HTML containing JavaScript, and the
user did a muting interaction, the malicious JavaScript could run when
rendering the "you just muted a topic" notification.

We did an audit for similarly problematic use of `.html`, and found
none; for the next release we'll be merging a series of changes to our
linter to prevent future instances of this being added.

Thanks to Suhas Sunil Gaikwad for reporting this issue.
2018-04-12 09:46:03 -07:00
YJDave
92a04b31a0 custom fields: Clean custom fields to use existing defined function. 2018-04-12 09:40:09 -07:00
guaca
a19daf0ab2 Settings: Fix vertical spacing.
Removed the top margin of input-group css
 to prevent the double margins. Also fixed the
 default-language positioning, and maintained
margin consistency in organization settings.

Fixes #8890.
2018-04-12 09:38:24 -07:00
Axel Tietje
8f984be457 docs: Fix typo in production docs. 2018-04-12 09:19:26 -07:00
Lyla Fischer
d291def7a1 user-docs subsystem: Fix broken markdown. 2018-04-12 09:09:56 -07:00
Lyla Fischer
390eeaab5b help: Remove follow-steps doc macro. 2018-04-11 16:44:08 -07:00
Lyla Fischer
00255ad7c0 help: Remove the go-to-the macro. 2018-04-11 16:44:08 -07:00
Shubham Padia
55619cbe70 browser-support: Add string.prototype.endswith polyfill.
String.prototype.endsWith is not supported in ie11.
Adds string.prototype.endswith package to dependencies and places
it at `common` entry point in webpack.assets.json.
2018-04-11 15:40:57 -07:00
Tim Abbott
e6833b6427 cleanup: Remove the legacy Dropbox file upload integration.
This has been hard-disabled for years, we have no plans to re-enable
it, and it has some hacky code in it.
2018-04-11 11:39:48 -07:00
Aditya Bansal
6c1a50da76 csp_reports: Add endpoint to handle logging of reports sent by clients. 2018-04-11 23:01:13 +05:30
YJDave
95461761e4 subscription: Show current user on top of subscribers list if present.
Fixes #9027.
2018-04-11 09:54:42 -07:00
YJDave
c662867f14 subscription: Add comments for recent changes covering corner cases. 2018-04-11 09:51:52 -07:00
Vishnu Ks
132754f2ef requirements: Downgrade pika to 0.11.0.
Downgrading as issue #8466 is not fixed yet.
2018-04-11 09:31:10 -07:00
Vishwesh Jainkuniya
383c62fb03 dev_login: Identify each user's realm when listing them.
This is a mobile-specific endpoint used for logging into a dev server.
On mobile without this realm_uri it's impossible to send a login request
to the corresponding realm on the dev server and proceed further; we can
only guess, which doesn't work for using multiple realms.

Also rename the endpoint to reflect the additional data.

Testing Plan:
Sent a request to the endpoint, and inspected the result.

[greg: renamed function to match, squashed renames with data change,
 and adjusted commit message.]
2018-04-10 17:03:36 -07:00
Tim Abbott
a463743107 puppet: Add Content-Security-Policy for user avatars.
This adds a basic Content-Security-Policy for user-uploaded avatars
served by the LOCAL_UPLOADS backend.

I think this is for now an unnecessary follow-up to
d608a9d315, but is worth doing because
we may later change what can be uploaded in the avatars directory.
2018-04-10 14:43:08 -07:00
Rhea Parekh
f7398cbb09 slack import: Implement custom profile fields.
Add custom profile fields in the slack converted
data 'realm' file.
Added tests for the custom profile fields.

Fixes #8928
2018-04-10 13:28:53 -07:00
Rhea Parekh
852e8516b4 slack import: Add custom profile fields.
Build CustomProfileField and CustomProfileFieldValue
for every user and process the field type after getting an
entire list of the custom fields.
2018-04-10 13:28:53 -07:00
Rhea Parekh
ccefaf7b26 scripts: Remove the depreciated script 'postgres-reset-sequences'. 2018-04-10 13:07:14 -07:00
Marco Burstein
c36a658fee uploads: Fix the upload progress bar.
There was already a progress bar set up, but it became non-functional
after refactoring.  This fixes it.

The default animation was getting cut off when `uploadFinished` is
called, so we add a delay before removing the upload bar to make it
get to the end.

Tweaked by tabbott to have a more natural feeling animation setup
(where we don't animate the width adjustments; just the disappearance
of the bar).

Fixes #8863.
2018-04-09 22:53:06 -07:00
Tim Abbott
a4def8d409 copy_and_paste: Re-disable copy-paste handler in production.
This reverts commit 6e048c5d3f.

See #8963 for the main issue we need to fix before re-enabling this;
basically, some combination of toMarkdown and the way text/html gets
written was introducing a lot of bonus/bogus whitespace, both in the
form of newlines and spaces converted to `&nbsp;`.
2018-04-09 22:10:28 -07:00
rht
a183186672 slack importer: User session.get to recycle previous connections. 2018-04-09 22:02:01 -07:00
Shubham Dhama
b650b6b38c markdown: Add @stream as an alias for @all.
Fixes: #8930.
2018-04-09 16:35:14 -07:00
Shubham Dhama
771db7fb90 compose typeahead: Refactor repeating code for all and everyone mentions.
This is minor refactor with which we can simply add other aliases for
"all" and "everyone" mentions.
2018-04-09 16:35:14 -07:00
Marco Burstein
7c66d11781 compose: Show avatars for people in typeahead autocompletes.
`@everone` and `@all` will have a megaphone icon from FontAwesome in
place of the avatar.

Also, fix the `composebox_typeahead` tests to account for the images.

Fix #6635.
2018-04-09 15:47:11 -07:00
Tim Abbott
9b8dd4f125 install-yarn: Fix buggy status check for the signature.
Apparently, they added a new signing key instance, and so checking
whether the old key exists doesn't work anymore.
2018-04-09 15:09:37 -07:00
Aditya Bansal
d608a9d315 uploads: Add Content-Security-Policy for user uploads.
This adds a basic Content-Security-Policy for user-uploaded files with local uploads.

While over time, we plan to add CSP for the main site as well, this CSP is particularly
important for the local-uploads backend, which often shares a domain with the main site.
2018-04-09 14:43:02 -07:00
Priyank
ee078c372f install-node: Upgrade node, yarn, and nvm.
node -> v8.9.4
yarn -> 1.5.1
nvm -> 0.33.8

Also updates a test in timerender.js which depends on time
provided by node which is now changed in newer release.

Some changes have been made in circeci script, we just create ~/.config
directory and chown it to circleci user so installing new version of yarn
does not cause any ci failure on circleci during provision.
2018-04-09 13:56:48 -07:00
Shubham Padia
57a494f94d browser-support: Add codepointat polyfill to common entry point.
Adds string.prototype.codepointat which was added as a polyfill
earlier to the project but was not added to `common` entry point.
2018-04-09 12:16:19 -07:00
Shubham Padia
b906562f22 browser-support: Add string.prototype.startsWith polyfill.
Fixes #8944.
Adds string.prototype.startswith package to dependencies and places
it at `common` entry point in webpack.assets.json. As common.js is
loaded on all code paths first, there is no need to place this package
into other entry points.
2018-04-09 12:16:19 -07:00
Tim Abbott
37a83285c4 people: Clean up now-unnecessary url variable. 2018-04-09 12:12:44 -07:00
Tim Abbott
40421c5000 people: Refactor small_avatar_url logic for emails. 2018-04-09 12:12:44 -07:00
Tim Abbott
dfac0302fc people: Extract small_avatar_url_for_person.
This is intended to be used in places like compose typeahead to
display users' avatars.
2018-04-09 12:12:44 -07:00
Tim Abbott
3bfd96d8ed people: Use a return for message.avatar_url code path.
This helps clean up this code path a bit.
2018-04-09 12:07:41 -07:00
Tim Abbott
5bcfecd0dc people: Extract gravatar_url_for_email. 2018-04-09 12:07:41 -07:00
rht
7a8655cc50 Slack importer: Add test for Slack channel mention to Zulip stream mention. 2018-04-09 10:47:39 -07:00
rht
630adb406b Slack importer: Map Slack channel mentions to Zulip stream mentions. 2018-04-09 10:47:39 -07:00
Rhea Parekh
035c440ff3 import script: Support import custom profile fields.
Import of Custom profile fields is only supported for slack
import script for now.
2018-04-09 10:45:35 -07:00
Tim Abbott
c41d7ee300 slack import: Write reasonable multi-line JSON.
This is a lot better for debugging.
2018-04-09 10:45:35 -07:00
YJDave
025956482a subscription: Fix error in appending current user on top of subscriber list. 2018-04-08 16:54:12 -07:00
YJDave
f5a7d125c9 subscription: Clean functions peer_subscribe and peer_unsubscribe. 2018-04-08 16:54:12 -07:00
YJDave
dcf9355502 subs.js: Rename function to check_button_for_sub to be more specific. 2018-04-08 16:54:12 -07:00
YJDave
ed70a92ed3 subscription: Fix error in being re-subscribed to private stream.
Fixes #9023
2018-04-08 16:54:12 -07:00
YJDave
24f51739eb subscription: Add real time sync for user-just-deactivated case.
Currently, stream subscriptions aren't getting updated without
hard reload when user is deactivated in realm.

Fix this issue by updating stream subscription widgets on user
deactivation event.

Fixes #5623
2018-04-08 16:54:12 -07:00
YJDave
cf40536ed2 stream_edit.js: Add helper func to rerender all subscriptions settings.
This add the function to rerender subscriptions settings which includes
subscriber count and subscriber list only if subscriptions tab is active.
2018-04-08 16:54:12 -07:00
YJDave
211eba2c56 stream_edit.js: Add helper func to check if sub settings tab is active.
This commit adds a new helper func to check if sub settings tab
is active or not and remove function `add_me_to_member_list`
function from `static/js/stream_edit.js`, cause we don't need to
render subscribers for particular case, as we are already doing that.
2018-04-08 16:54:12 -07:00
YJDave
386c56b466 stream_data.js: Replace user_email with user_id in func is_user_subscribed. 2018-04-08 16:54:12 -07:00
Tim Abbott
c3df378ca1 slack import: Document that this is a new feature in 1.8. 2018-04-08 07:11:07 -07:00
Rhea Parekh
ed7127c8b4 import script: Delete medium sized avatars if it exists.
Deletion of medium sized image is done if it exists before calling the
function 'ensure_medium_avatar_image', to avoid potentially confusing
problems with left-over medium-size avatar images from a previous run
being used when repeatedly importing the same realm in a development
environment..

Fixes #8949.
2018-04-08 07:04:24 -07:00
Balaji2198
c63d1c9205 node tests: Cover compose_not_subscribed in compose.js.
This commit covers the node tests to close(X) button and
subscribe button click handlers in compose.js.
2018-04-07 20:23:21 -07:00
Balaji2198
47f9e8319c compose: Close the compose error message box on clicking X. 2018-04-07 20:23:21 -07:00
Shubham Dhama
f6d73a7444 settings: Fix label for message_content_in_email_notifications.
This was a regression introduced in deduplication of settings
template.
Fixes: #9021.
2018-04-07 20:22:33 -07:00
YJDave
21d1133c4f subscriptions: Clear email address on unsubscriptions from stream.
Currently, even after unsubscribing from private/public stream
email address of stream is still present in html widgets hidden.
Cause we don't clear email address on unsubscription event.

This clears email address from widget when user unsubscribe
from any stream.
2018-04-07 20:10:45 -07:00
YJDave
f15ddc93e0 create stream: Fix stream email not rendering on stream creation.
Fixes #8817
2018-04-07 20:10:45 -07:00
Aditya Bansal
b9f1acb300 linter: Enforce 2 space indents on tags spread over multiple lines.
We make some specific cases of tags use 2 space indents.
The case description:
* A tag with opening tag spread over multiple lines and closing tag
on the same line as of the closing angle bracket of the opening tag.
* A tag with opening tag spread over multiple lines and closing tag
not on the same line as of the closing angle bracket of the opening
tag.

Example:
Case 1:

Not linted:
<button type="button"
class="btn btn-primary btn-small">{{t "Yes" }}</button>

After linting:
<button type="button"
  class="btn btn-primary btn-small">{{t "Yes" }}</button>

Case 2:

Before linting:
<div class = "foo"
     id = "bar"
     role = "whatever">
     {{ bla }}
</div>

After linting:
<div class = "foo"
  id = "bar"
  role = "whatever">
    {{ bla }}
</div>
2018-04-07 20:08:44 -07:00
Aditya Bansal
550222dede linter: Make multiline handlebar singleton tags use 2 space indentation. 2018-04-07 20:08:38 -07:00
Aditya Bansal
2fe012ffff linter: Make html singleton tags use 2 space indentation. 2018-04-07 20:08:31 -07:00
Cynthia Lin
7eacf2aa9a org settings: Offset border to prevent adding extra height on hover.
Fixes #8996.
2018-04-07 20:06:40 -07:00
Gooca
3ed5a64e13 Dark-mode: Update rail-y to match dark theme. 2018-04-07 20:03:19 -07:00
rht
f6feac1316 Slack importer: Map Slack command for mentions to Zulip's all.
Fixes #9003.
2018-04-07 20:02:39 -07:00
Rhea Parekh
e037c2f93e import script: Fix upload links.
Rendered content is None for Slack imports, hence it is replaced only
for Zulip->Zulip imports.

Fixes #8959.
2018-04-07 20:01:20 -07:00
Rhea Parekh
b3f951d2cf import script: User profile ids should be allocated before allocating bot ids. 2018-04-07 13:28:33 +05:30
Vishnu Ks
e92838a31f registration: Catch email validation error and show error message. 2018-04-06 15:18:32 -07:00
Shubham Dhama
0e6757af5c org settings: Change default realm description to empty string.
This fixes a traceback that users would get when editing the realm
description just after creating a new organization.
2018-04-06 15:15:47 -07:00
Alyssa Wagenmaker
df666c3dfc test-backend: Report fully covered files still in not_yet_fully_covered. 2018-04-06 17:29:18 -04:00
Ben Reeves
29a079ebbf test-backend: Remove fully covered files from not_yet_fully_covered. 2018-04-06 17:29:18 -04:00
Tim Abbott
65c4a43a82 lint: Fix errors with stats.js with new eslint.
This (for ... in) syntax we shouldn't be using anyway, but this at
least fixes the worst aspect of it.
2018-04-06 12:42:19 -07:00
Shubham Dhama
4b7ce531c3 settings: Revert "loader" indicator from fontawesome to SVG.
This reverts loader indicator from the new fontawesome
`icon-button-loading` to previous SVG one, this change is only reflected
to those loaders which use `loader.handlebars` template for
loading indication(because there are some indicators like "Save changes"
in org settings which don't use loader.handlebars).

This main problem with this indicator is that it is bit
inconsistent with other places where we use `loader.handlebars` like
loading Zulip icon which appears while fetching old messages.
2018-04-06 12:32:45 -07:00
Aastha Gupta
c852185e9d stream settings: Make deactivate stream handler global.
Configure the click event handler for #do_deactivate_stream_button
once to avoid adding click handlers for it more than once.

Fixes #8979
2018-04-06 12:25:42 -07:00
Tim Abbott
d1c57df0ca estlint: Upgrade version of eslint. 2018-04-06 11:58:58 -07:00
Rhea Parekh
2baa9bc16e Import: Add subdomain in the import script.
Also remove user input of subdomain in the slack data
conversion script.
2018-04-06 09:12:56 -07:00
Tim Abbott
ad861c5fae messages: Improve comment on need_messages. 2018-04-06 08:57:46 -07:00
Priyank
3413faee14 provision: Bump provision version after clipboard package update.
The provision wasn't bumped in https://github.com/zulip/zulip/pull/8984
which caused some js exceptions.
2018-04-06 11:12:35 -04:00
Eeshan Garg
42bbfea775 webhooks/splunk: Update docs to conform to style guide. 2018-04-05 23:28:27 -07:00
Eeshan Garg
7b1ce446cf webhook/opsgenie: Update docs to conform to style guide. 2018-04-05 22:47:22 -07:00
Eeshan Garg
2e700477e3 webhooks/groove: Update docs to conform to style guide. 2018-04-05 22:47:21 -07:00
Armaan Ahluwalia
7b8da9b6c0 settings: Changed checkbox and close icons on settings.
Introduced a new checkmark icon in the settings page
from entypo ( www.entypo.com )  to make icons more
consistent between user and organization settings.
2018-04-05 21:49:13 -07:00
Armaan Ahluwalia
58d07fabef settings: Change save and discard button look and feel.
This commit changes the way the save and discard buttons on the
organization profile, settings and permissions tabs look and fades
them out after a delay. It also cleans up the code a bit in the
settings_org.js file. It introduces changes to the css in
settings.css as well as the template for save-discard buttons.

It also fixes a bug on the user settings whereby if an option
that requires reload is clicked before clicking an option that does
not require reload, the reload message is erased. This could create
an issue where the user is not aware that a reload is required.
The loader is also changed to using fa-icon as loading spinner on
user settings and the colors are tweaked a little bit.
2018-04-05 21:49:12 -07:00
Greg Price
9a0fdf5b8d settings: Add a few more section headings.
This should help a bit more in making this file navigable.

I think there's further work that could be done to organize the
settings better: e.g., group LDAP with the auth section; separate
resource limits, from debugging and error reporting, from configuring
service dependencies like Redis and Rabbit.  That'd require reordering
many settings, and also taking a closer look at many settings one by
one in order to do a good job.  Leaving that for another day.
2018-04-05 21:24:48 -07:00
Greg Price
9a9d3097be settings: Add some visual weight to the section headings.
I've found this file hard to navigate for a while.  We actually have a
little hierarchy of section headings which applies to a lot of the
file already; make the boundaries bolder.
2018-04-05 21:24:48 -07:00
Greg Price
f597f0b52e settings: Revise block comment at top of file.
Hopefully this is a bit clearer to read.
2018-04-05 21:24:48 -07:00
Greg Price
6396b3aef7 docs/production: Make an editing pass through the SMTP doc.
In addition to many small edits for formatting and clarity, a few more
significant changes:

* In the main instructions, refer specifically to restarting the
  server and to testing that the config works.

* Add SendGrid to the recommended list, as it seems like people
  give it a somewhat stronger reputation these days than Mailgun.

* Discuss EMAIL_USE_TLS and EMAIL_PORT along with host, user, and
  password in the "free services" section.  Though those bullets feel
  kind of duplicative to me already.
2018-04-05 21:24:48 -07:00
Greg Price
b9f1f9c0ae docs/production: Reorganize SMTP docs a bit.
Let's get right to the point of how to configure SMTP once you know
what you want.  That section is pretty short anyway; and we can have
a first step direct the reader to our suggestions if they don't know
what service they want to use.

Also adjust the hierarchy of the headings: group the various
alternative email services under one heading, and group
troubleshooting together under an independent heading.

Also correct what we say about EMAIL_PORT: the Django default is
apparently 25, so if the provider *does* use the usual port 587
then we'll need the port to be set.
2018-04-05 21:24:48 -07:00
Greg Price
9956d61e20 settings: Revise comments on SMTP / outgoing email settings.
Add a clear heading, and use fewer words and simpler sentences.  Also
explain the password thing a bit more, and put that more inline next
to the username.

Also, on checking the Django docs, the default for EMAIL_USE_TLS
is False and for EMAIL_PORT is 25.  So most admins, certainly any that
are using an SMTP service on the public Internet (that is at all
decently run), will need to set those settings.  Mention that.
2018-04-05 21:24:48 -07:00
Tarun Kumar
c53458c9c0 user-groups: Add template for non-editable groups. 2018-04-05 17:40:12 -07:00
Tarun Kumar
5c11ab857e pills: Add exportable function for creating non-editable pills. 2018-04-05 17:40:12 -07:00
Armaan Ahluwalia
9a6a82516d settings: Make sticky feedback not disappear after delay.
This commit adds the ability to pass the sticky option to
in the change_display_setting function in order to have the
feedback element remain visible instead of fading out which
is the default behavior. Also passes true for that option in
two instances on the page.
2018-04-05 17:18:39 -07:00
Armaan Ahluwalia
381e498343 settings: Fixes spinners and fades out feedback in settings.
This commit changes the do_settings_change function so that it
defaults to showing the loading spinner for 500ms before fading
out the the feedback element. It also adds a sticky option so you
can override the fading out of the feedback element and have it
remain visible.
2018-04-05 17:18:35 -07:00
Armaan Ahluwalia
95634b9d17 ui: Add ability to hide ui feedback messages.
This adds the option to hide the container element after a given
duration in the message and success functions in the ui_report module.
2018-04-05 17:17:08 -07:00
Balaji2198
605916f6d7 compose: Add subscribe button to the not subscribed stream error message.
Before that, we needed to go the stream settings to subscribe to a
particular stream.

Fixes #3877.
2018-04-05 17:15:18 -07:00
Shubham Dhama
d3627ab419 docs: Update new-feature-tutorial.
This updates the new-feature-tutorial for the recent changes done
in org settings subsystem.

Edited significantly by tabbott, mostly for grammar.
2018-04-05 15:51:51 -07:00
Cynthia Lin
c5d5efa9be portico-signin: Remove fixed-width styling for OR lines.
Fixes #8977.
2018-04-05 15:25:36 -07:00
Tim Abbott
b12368aec5 compose: Fix fading when topic changes on re-narrow.
Now that we're changing the topic on re-narrow more frequently, we
need to ensure that we update the compose_fade state when we do so.
2018-04-05 15:21:02 -07:00
Tim Abbott
5a5b4730f1 compose_actions: Keep the compose box open on topic change.
This tweak to our compose on-topic-narrow logic may help make it a bit
easier to do quick replies without needing to re-open compose.  I'm
not 100% confident this actually makes Zulip better, but it's worth
testing and getting some feedback.

Fixes #6473.
2018-04-05 15:17:40 -07:00
Tim Abbott
b9acdd947a compose_state: Re-fade message list when switching topics.
For a non-empty compose box, we previously considered closing the
compose box when switching topic narrows with content in the compose
box; now we leave it open unconditionally.

Part of #6473.
2018-04-05 15:17:40 -07:00
Priyank
f5acbcb4c8 clipboard: Update clipboard to v2.0.0 to avoid variable name conflict.
It turns out, now we have a new standard way to access clipboard by
`Clipboard` method and currently this conflict with the constructor
exported by clipboard package. The new update v2.0.0 was released to address
this issue. The new version just exports the constructor as `ClipboardJS`.

Ref: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
Ref: https://github.com/zenorocha/clipboard.js/issues/468
2018-04-05 15:09:00 -07:00
Eeshan Garg
d03d2808b2 integrations css: Increase top/bottom margin for h3 headings.
The margin between the first sentence/heading for the
instructions and the numbered list that follows was too small,
which made headings look very awkward.
2018-04-05 14:48:37 -07:00
Eeshan Garg
231f1b3492 integrations css: Properly indent <ul>'s nested inside <ol>'s.
The indentation was off because there was no CSS anywhere that
properly indented <ul> elements that were nested inside <ol>
lists with custom numbering.
2018-04-05 14:48:37 -07:00
Eeshan Garg
eb9902e77f integrations css: Properly indent multi-paragraph list items.
Since every <li> element is prefixed by a custom list number, in
<li> elements with multiple <p> elements inside, the <p> elements
after the first one did NOT take into account the space occupied
by the custom list number, which resulted in inconsistent
indentation. Now, it does!
2018-04-05 14:48:37 -07:00
Eeshan Garg
d6cc1cfbc9 integrations: Render unordered lists with bullets.
Previously, the CSS for ordered lists also applied to unordered
lists, so unordered lists were rendered without any kind of bullets
or markers.
2018-04-05 14:48:37 -07:00
Eeshan Garg
42fb91de33 macros: Update link to installation instructions for API bindings.
This changed a while ago when we restructured our API docs.
2018-04-05 14:48:37 -07:00
Eeshan Garg
902ab01785 webhooks/trello: Update docs to conform to style guide.
This one is one of the most tedious to set up and get working.

We now also rely on the Trello scripts available as part of the
`python-zulip-api/zulip` API package to make the setup process
easier.
2018-04-05 14:48:37 -07:00
Tim Abbott
b0b134cb4c help: Clean up settings_html and subscriptions_html.
After some thinking, I don't think there's any actual value to doing
the ../ style relative links here, whereas there is actual harm from
the links being slightly broken in the current model.  We fix this by
just using /#settings as the URL.

Fixes #8978.
2018-04-05 14:48:26 -07:00
Rishi Gupta
a29b1c1569 help: Update change-your-name. 2018-04-05 14:46:32 -07:00
Rishi Gupta
f4737e77b0 help: Update view-an-image-at-full-size. 2018-04-05 14:46:32 -07:00
Rishi Gupta
efecad2355 help: Update view-and-edit-your-message-drafts. 2018-04-05 14:46:32 -07:00
Ben Reeves
05d3073960 vagrant: Fix link to testing docs in motd.
Change https://zulip.readthedocs.io/en/latest/testing.html to the new 
https://zulip.readthedocs.io/en/latest/testing/testing.html
2018-04-05 14:41:38 -07:00
Steve Howell
36844418e9 bug fix: Respect include_history for certain queries.
For certain queries where both include_history and
use_first_unread_anchor are set to True, we were excluding
historical rows.  Now we only use the use_first_unread_anchor
flag to filter rows that we use to find the anchor, without
having it filter the actual search results.

The bug went unreported for a long time, because it only
affected mobile users who had newly subscribed to streams.

Note that we make a small change to the test called
test_use_first_unread_anchor_with_muted_topics, which has
a very scary comment about being "arcane" and "be
absolutely sure you know what you're doing."  I think it's
fine.

Also, the new test code would fail before this fix, so it
should help prevent future regressions.

Fixes #8958
2018-04-05 17:16:41 -04:00
Steve Howell
b64117d872 refactor: Build query in find_first_unread_anchor().
This is a bit more than a pure refactor, because we duplicate a
chunk of code to calculate a query inside of
find_first_unread_anchor(), so we're doing a bit more work
than before.

We need this refactoring to start decoupling find_first_unread_anchor
from get_messages_backend for the case where include_history is
True.  This will happen in a subsequent commit.

The only test that changes here is a direct test on
find_first_unread_anchor().  All other tests pass without
modification, and we have decent coverage on get_messages_backend.
2018-04-05 17:16:41 -04:00
Steve Howell
345d44b5f1 Extract get_base_query_for_search(). 2018-04-05 17:16:41 -04:00
Steve Howell
59a9b69c25 Simplify search code for keyword searches.
We use an array now to build up the list of search operands and
then consolidate the special search handling after the loop (which
means setting the flag, putting two more columns in the query, and
using ' '.join to build the string).
2018-04-05 13:27:31 -07:00
Steve Howell
d521906fb6 search refactor: Extract add_narrow_conditions().
This code was basically pulled from two near-to-each-other
sections of get_messages_backend, and it does an early
return for narrow-is-None.
2018-04-05 13:27:31 -07:00
Steve Howell
3ac660d972 minor: Show narrow for UnicodeDecodeError.
We have a debugging statement for some obscure errors we get
when narrows have search terms.  We now show all the narrow
operators.  This isn't really to improve debugging; it's more
to make it easier in the next commit to extract a function
that would make search_term have to be passed back in a tuple.
But it shouldn't hurt debugging either.
2018-04-05 13:27:31 -07:00
Tim Abbott
ed5b374ffa help docs: Fix a sorta buggy URL test. 2018-04-05 13:07:34 -07:00
Tim Abbott
d7658bbec5 test_docs: Add an end-to-end test for HTML settings links.
This would have caught the issue fixed in the last few commits.
2018-04-05 12:22:41 -07:00
Tim Abbott
57ca19392e help: Make API context available to sidebars.
I am not a fan of this API (where we need to pass context into
`render_markdown_path` directly), but it seems more robust to pass
this in directly.
2018-04-05 12:22:41 -07:00
Tim Abbott
98889608a2 help: Fix structure of markdown context logic.
Refactoring in this file had resulted in the logic for
html_settings_link being duplicated and extra logic being needed to
ensure these variables were set where they were needed.

This fixes subscriptions_html not being rendered properly in the /help
and /api pages, in addition to removing duplicate code.
2018-04-05 12:22:41 -07:00
Tim Abbott
f1ece37455 help: Fix context not being passed properly to rendering.
This contributed to settings_html, subscriptions_html and friends not
being rendered properly in the /help/ views.
2018-04-05 11:43:48 -07:00
Nikhil Kumar Mishra
2cf32bda12 embed link: Add test for link_embed_data_from_cache. 2018-04-05 10:48:40 -07:00
Steve Howell
a0aa8d4b11 Add test for find_first_unread_anchor(). 2018-04-05 09:55:54 -07:00
Steve Howell
4cba679d38 Move code for find_first_unread_anchor().
This is a pure refactoring and just pulls the function out
to the top level of the module.  (The prior commit extracted
it inside a larger function to make a nicer diff.)
2018-04-05 09:55:54 -07:00
Steve Howell
d8a95c6517 Extract find_first_unread_anchor().
This is a pure refactoring.
2018-04-05 09:55:54 -07:00
Shubham Padia
6262460773 refactor: Rename mark_message(s)_as_read to notify_server_message(s)_read.
Fixes #8965.
Mark_message(s)_as_read is used in marking a message as having been
read by the browser, rename it to notify_server_message(s)_read to
avoid any confusion.
2018-04-05 09:54:48 -07:00
Rishi Gupta
5740af27d6 help: Update import-data-from-slack. 2018-04-05 08:49:40 -07:00
Steve Howell
c164d07baa zjquery: Enforce only one arg for $(...) function. 2018-04-05 10:46:45 -04:00
Steve Howell
4216b81e93 Fix Subscribed/All Streams bug.
We've had a longstanding bug where the streams settings code
was getting an i18n'ed value in the middle of a callback from
the toggle component, so it would have been broken for
non-English sites.  And then a recent cleanup of the toggle
code introduced a bug where the callback-in-the-callback was
getting stale state, so English sites broke too.

This fix just simplifies everything by using the key that
comes into our callback to determine whether we filter or not.

Fixes #8945
2018-04-04 16:37:39 -07:00
Steve Howell
27770d7f6b Fix recent pitfall in toggle component.
This is a recent regression where we I refactored the toggle
component.  For some reason the old code was waiting until
after the callback to set some of its state, and I did the
same thing when I simplified how the state was stored.

Under the old code, this didn't manifest as a bug, although
the old code was problematic for other reasons.

This "fix" doesn't actually change anything user facing, as the
follow up commit fixes the proximal problem more directly. And
the toggle component is still prone to people writing code that
tries to inspect the state of the widget as it's being built.
2018-04-04 16:37:39 -07:00
Steve Howell
0e7073ec29 Fix keyboard handling for info overlays.
For info overlays (keyboard/markdown/search help) we now let
the modal portions of the widget have focus, so that you can
page around.  And then tab switching still works with the arrow
keys.
2018-04-04 16:37:39 -07:00
Steve Howell
bd591424e2 Add keydown_util.js module.
This is a pretty thin abstraction to prevent having to put
magic numbers in code, doing the which/keyCode hack, and remembering
to all preventDefault.

Hopefully we'll expand it to handle things like shift/alt keys
for components that want their own keyboard handlers (vs. going
through hotkey.js).
2018-04-04 16:37:39 -07:00
Tim Abbott
c06565d909 users: Improve testing for user_ids_to_users. 2018-04-04 16:31:30 -07:00
novokrest
4d2082ab14 actions.py: Obtain bot profiles by using users.user_ids_to_users().
Remove models.get_user_profiles_by_ids() and
obtain user's bots profiles in actions.get_service_dicts_for_bots() by
users.user_ids_to_users() instead of models.get_user_profiles_by_ids().
Fixes #8939
2018-04-04 16:24:55 -07:00
novokrest
807a6ccf2c users.py: Implement user_ids_to_users() by generic_bulk_cached_fetch().
Optimize users.user_ids_to_users() implementation
by using generic_bulk_cached_fetch() to obtain user profiles
2018-04-04 16:24:55 -07:00
Tim Abbott
53e47e6991 messages: Modify access_message for is_history_public_to_subscribers.
This completes the Message side of #2745.
2018-04-04 16:18:47 -07:00
Tim Abbott
bec71d7a50 messages: Add a server-level setting to control private stream history.
We don't indend for this server-level setting to exist in the long
term; the purpose of this is just to make it easy to test this code
path for development purposes.

This implements much of the Message side part of #2745.
2018-04-04 16:18:46 -07:00
Tim Abbott
228f41e916 messages: Pass UserProfile to is_public_stream_by_name and rename.
The new name can_access_stream_history_by_name gets to the point of
what this function actually does.  And passing in a user object lets
us define what this does based on the user subscribed.
2018-04-04 15:13:11 -07:00
Tim Abbott
5e82d750c5 get_messages: Refactor ok_to_include_history to accept a UserProfile.
If we make history accessible to some stream subscribers of private
streams, we'll need the UserProfile to be available here.
2018-04-04 15:06:53 -07:00
Shubham Dhama
2aaad502b4 org settings: Hide "disable" option when setting already disabled.
Fixes: #8942.
2018-04-04 11:40:35 -07:00
Tim Abbott
721b4e8373 i18n: Fix strings for wildcard mentions.
First, "Notify stream" is a lot clearer than "Notify everyone";
second, these strings should be tagged for translation.
2018-04-04 11:26:36 -07:00
YJDave
aeef925b93 custom fields: Fix error in rendering long textual custom fields.
Currently, long textual fields are rendered as short textual fields
in UI, this bug was introduced because of our recent changes in
custom fields type.
2018-04-04 10:46:18 -07:00
YJDave
8bc181882a custom fields: Remove unused code for custom fields.
It removes code related to custom profile field's placeholder styling
and related to numeric custom fields, as recently we removed support
for numeric custom fields.
2018-04-04 10:46:18 -07:00
Tim Abbott
ecec489e7e docs: Further update the Slack import instructions.
* Removes the confusing notes about supporting only the Slack standard
  plan.
* Removes the confusing notes about importing users from a
  different organization.
* Clarifies a few sections, improves formatting, etc.
2018-04-04 10:38:29 -07:00
Rhea Parekh
48e219e880 docs: Update import data from slack doc. 2018-04-04 10:18:59 -07:00
Rhea Parekh
f4ad464d82 import script: Fix broken links to attachments.
The comments explain this pretty well, but basically because we
rewrite the realm ID during the import process, we need to edit all
the message bodies that link to an attachment to instead link to the
post-processed URL where that file will be hosted on the new server.

Fixes #8926.
2018-04-04 10:05:15 -07:00
Rhea Parekh
5a9cea4134 import script: re map foreign key of UserProfile.last_active_message_id. 2018-04-04 08:53:09 -07:00
Rhea Parekh
ed36314042 import script: Fix 're_map_foreign_keys' logging error. 2018-04-04 08:53:09 -07:00
Rhea Parekh
877c7760b7 import script: re_map Attachment foreign keys. 2018-04-04 08:53:09 -07:00
Eeshan Garg
96e01c0d27 pypi: Upgrade to release 0.4.3. 2018-04-03 18:11:25 -07:00
Balaji2198
591e152e38 org settings: Fix error handling for upload custom emoji. 2018-04-03 13:18:53 -07:00
Tim Abbott
9156591406 docs: Update changelog through current master. 2018-04-03 12:54:04 -07:00
Tim Abbott
682d4f2ea1 casper: Fix expected result for i18n test.
Now that this string is translated into German, the test should expect
it in German.
2018-04-03 11:05:48 -07:00
Aman Agrawal
38829032be docs: Correct broken link on gsoc-ideas.md.
Set correct link for summer-with-zulip.
2018-04-03 10:43:13 -07:00
399 changed files with 7314 additions and 5653 deletions

View File

@@ -19,12 +19,13 @@ jobs:
dirs=(/srv/zulip-{npm,venv}-cache)
sudo mkdir -p "${dirs[@]}"
sudo chown -R circleci "${dirs[@]}"
- restore_cache:
keys:
- v1-npm-base.trusty.1
- v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- restore_cache:
keys:
- v1-venv-base.trusty.1
- v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
- run:
name: install dependencies
@@ -50,11 +51,11 @@ jobs:
- save_cache:
paths:
- /srv/zulip-npm-cache
key: v1-npm-base.trusty.1
key: v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- save_cache:
paths:
- /srv/zulip-venv-cache
key: v1-venv-base.trusty.1
key: v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
# TODO: in Travis we also cache ~/zulip-emoji-cache, ~/node, ~/misc
# The moment of truth! Run the tests.
@@ -99,12 +100,13 @@ jobs:
dirs=(/srv/zulip-{npm,venv}-cache)
sudo mkdir -p "${dirs[@]}"
sudo chown -R circleci "${dirs[@]}"
- restore_cache:
keys:
- v1-npm-base.xenial.1
- v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- restore_cache:
keys:
- v1-venv-base.xenial.1
- v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
- run:
name: install dependencies
@@ -116,11 +118,11 @@ jobs:
- save_cache:
paths:
- /srv/zulip-npm-cache
key: v1-npm-base.xenial.1
key: v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- save_cache:
paths:
- /srv/zulip-venv-cache
key: v1-venv-base.xenial.1
key: v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
- run:
name: run backend tests

View File

@@ -15,7 +15,6 @@
"XDate": false,
"zxcvbn": false,
"LazyLoad": false,
"Dropbox": false,
"SockJS": false,
"marked": false,
"md5": false,
@@ -33,6 +32,7 @@
"server_events": false,
"server_events_dispatch": false,
"message_scroll": false,
"keydown_util": false,
"info_overlay": false,
"ui": false,
"ui_report": false,
@@ -167,7 +167,7 @@
"emoji_codes": false,
"drafts": false,
"katex": false,
"Clipboard": false,
"ClipboardJS": false,
"emoji_picker": false,
"hotspots": false,
"compose_ui": false,

View File

@@ -6,3 +6,5 @@ known_third_party = django, ujson, sqlalchemy
known_first_party = zerver, zproject, version, confirmation, zilencer, analytics, frontend_tests, scripts, corporate
sections = FUTURE, STDLIB, THIRDPARTY, FIRSTPARTY, LOCALFOLDER
lines_after_imports = 1
# See the comment related to ioloop_logging for why this is skipped.
skip = zerver/management/commands/runtornado.py

2
Vagrantfile vendored
View File

@@ -167,7 +167,7 @@ Welcome to the Zulip development environment! Popular commands:
* tools/lint - Run the linter (quick and catches many problmes)
* tools/test-* - Run tests (use --help to learn about options)
Read https://zulip.readthedocs.io/en/latest/testing.html to learn
Read https://zulip.readthedocs.io/en/latest/testing/testing.html to learn
how to run individual test suites so that you can get a fast debug cycle.
EndOfMessage'

View File

@@ -44,7 +44,7 @@ master_doc = 'index'
# General information about the project.
project = 'Zulip'
copyright = '2015-2017, The Zulip Team'
copyright = '2015-2018, The Zulip Team'
author = 'The Zulip Team'
# The version info for the project you're documenting, acts as replacement for
@@ -52,9 +52,9 @@ author = 'The Zulip Team'
# built documents.
#
# The short X.Y version.
version = '1.7+git'
version = '1.8'
# The full version, including alpha/beta/rc tags.
release = '1.7.1+git'
release = '1.8.0'
# This allows us to insert a warning that appears only on an unreleased
# version, e.g. to say that something is likely to have changed.

View File

@@ -7,111 +7,80 @@ All notable changes to the Zulip server are documented in this file.
This section lists notable unreleased changes; it is generally updated
in bursts.
**Highlights:**
### 1.8.0 -- 2018-04-17
- Added a user setting to choose the emoji set used in Zulip: Google,
Twitter, Apple, or Emoji One.
- Added a video call integration powered by Jitsi.
**Highlights:**
- Dramatically simplified the server installation process; it's now possible
to install Zulip without first setting up outgoing email.
- Added certbot support to the installer for getting certificates.
- Added support for mentioning groups of users.
- Added experimental support for importing an organization's history
from Slack.
- Added a new "night mode" theme for dark environments.
- Added experimental support for importing an organization from Slack.
- Overhauled our settings system to eliminate the ugly "save changes"
button system.
- Rewrote our API documentation to be much more friendly and
expansive; it now covers most important endpoints, with nice examples.
- Added a video call integration powered by Jitsi.
- Lots of visual polish improvements.
- Countless small bugfixes both in the backend and the UI.
**Security and privacy:**
- Several important security fixes since 1.7.0, which were released
already in 1.7.1 and 1.7.2.
- The security model for private streams has changed. Now
organization administrators can remove users, edit descriptions, and
rename private streams they are not subscribed to. See Zulip's
security model documentation for details.
- Lots of visual polish improvements.
**Full feature changelog:**
- New integrations: ErrBot, GoCD, Google Code-In, Opbeat, Groove, Raygun,
Insping, Dropbox, Front, Intercom, Statuspage.io, Flock and Beeminder.
- The local uploads backend now does the same security checks that the
S3 backend did before serving files to users.
- Added support for users in multiple realms having the same email.
- Added support for embedded interactive bots.
- Added inline preview + player for Vimeo videos.
- Added a setting to allow users to delete their messages.
- On Xenial, the local uploads backend now does the same security
checks that the S3 backend did before serving files to users.
Ubuntu Trusty's version of nginx is too old to support this and so
the legacy model is the default; we recommend upgrading.
- Added an organization setting to limit creation of bots.
- Refactored the authentication backends codebase to be much easier to
verify.
- Added a user setting to control whether email notifications include
message content (or just the fact that there are new messages).
**Visual and UI:**
- Added a user setting to translate emoticons/smileys to emoji.
- Added a user setting to choose the emoji set used in Zulip: Google,
Twitter, Apple, or Emoji One.
- Expanded setting for displaying emoji as text to cover all display
settings (previously only affected reactions).
- Overhauled our settings system to eliminate the old "save changes"
button system.
- Redesigned the "uploaded files" UI.
- Redesigned the "account settings" UI.
- Redesigned error pages for the various email confirmation flows.
- Our emoji now display at full resolution on retina displays.
- Improved placement of text when inserting emoji via picker.
- Improved the descriptions and UI for many settings.
- Improved visual design of the help center (/help/).
**Core chat experience:**
- Added support for mentioning groups of users.
- Added a setting to allow users to delete their messages.
- Added support for uploading files in the message-edit UI.
- Added new event types to several webhook integrations.
- Added a display for whether the user is logged-in in logged-out
pages.
- Added support for hosting multiple domains, not all as subdomains of
the same base domain.
- Added a new /team/ page explaining the team, with a nice
visualization of our contributors.
- Added support for default bots to receive messages when they're
mentioned, even if they are not subscribed.
- Added support for inviting a new user as an administrator.
- Added a new organization settings page for managing invites.
- Added a user setting to control whether the organization's name is
included in email subject lines.
- Added support for clicking on a mention to see a user's profile.
- Added new compose features for pasting HTML.
- Redesigned the compose are for private messages to use pretty pills
rather than raw email addresses to display recipients.
- Added new ctrl+B, ctrl+I, ctrl+L compose shortcuts for inserting
common syntax.
- Added warning when linking to a private stream via typeahead.
- Added rate-limiting on inviting users to join a realm (prevents spam).
- Added support for automatically-numbered markdown lists.
- Added a big warning when posting to #announce.
- Added a user setting to control whether email notifications include
message content (or just the fact that there are new messages).
- Added a notification when drafts are saved, to make them more
discoverable.
discoverable.
- Added a fast local echo to emoji reactions.
- Added new "basics" section to keyboard shortcuts documentation.
- Added a new ">" keyboard shortcut for quote-and-reply.
- Added a new "p" keyboard shortcut to just to next unread PM thread.
- Added support for overriding the topic is all incoming webhook integrations.
- Added a new nagios check for the Zulip analytics state.
- Added a menu item to mark all messages as read.
- Added support for logging into the mobile apps with RemoteUserBackend.
- Added an organization setting to disable welcome emails to new users.
- Added traffic statistics (messages/week) to the "Manage streams" UI.
- Added a display setting to translate emoticons/smileys to emoji.
- Added an organization setting to ban disposable email addresses
(I.e.. those from sites like mailinator.com).
- Added a server setting to control whether digest emails are sent.
- Links to logged-in content in Zulip now take the user to the
appropriate upload or view after a user logs in.
- Incoming webhooks now send a private message to the bot owner for
more convenient testing.
- Rewrote documentation for many integrations to use a cleaner
numbered-list format.
- Renamed "Home" to "All messages", to avoid users clicking on it too
early in using Zulip.
- Messages containing just a link to an image (or an uploaded image)
now don't clutter the feed with the URL: we just display the image.
- Refactored the authentication backends codebase to be much easier to
verify.
- Expanded setting for displaying emoji as text to cover all display
settings (previously only affected reactions).
- Redesigned the API for emoji reactions to support the full range of
how emoji reactions are used.
- Migrated the codebase to use the nice Python 3 typing syntax.
- Optimized how user avatar URLs are transmitted over the wire.
- Optimized message sending performance a bit more.
- Split the Notifications Stream setting in two settings, one for new
users, the other for new streams.
- Fixed numerous issues in the "stream settings" UI.
- Fixed most of the known (mostly obscure) bugs in how messages are
formatted in Zulip.
- Fixed "more topics" to correctly display all historical topics for
public streams, even though from before a user subscribed.
- Fixed several bugs around interacting with deactivated users.
- Added a menu item to mark all messages as read.
- Fixed image upload file pickers offering non-image files.
- Fixed some subtle bugs with full-text search and unicode.
- Fixed bugs in the "edit history" HTML rendering process.
- Fixed several hotkeys scope bugs.
- Fixed popovers being closed when new messages come in.
- Fixed unexpected code blocks when using the email mirror.
- Fixed clicking on links to a narrow opening a new window.
@@ -119,47 +88,132 @@ discoverable.
- Fixed layering issues with mobile Safari.
- Fixed several obscure real-time synchronization bugs.
- Fixed handling of messages with a very large HTML rendering.
- Fixed buggy APNs logic that could cause extra exception emails.
- Fixed several bugs around interacting with deactivated users.
- Fixed interaction bugs with unread counts and deleting messages.
- Fixed support for replacing deactivated custom emoji.
- Fixed a missing dependency for the localhost_sso auth backend.
- Fixed uploading user avatars encoded using the CMYK mode.
- Fixed scrolling downwards in narrows.
- Fixed numerous subtle bugs with the stream creation UI.
- Dramatically improved organization of developer docs.
- Statistics on translation percentages now include mobile apps.
- Optimized how user avatar URLs are transmitted over the wire.
- Optimized message sending performance a bit more.
- Fixed a subtle and hard-to-reproduce bug that resulted in every
message being condensed ([More] appearing on every message).
- Improved typeahead's handling of editing an already-completed mention.
- Improved syntax for inline LaTeX to be more convenient.
- Improve keyboard navigation of left and right sidebars with arrow keys.
- Changes the URL scheme for stream narrows to encode the stream ID,
so that they can be robust to streams being renamed. The change is
backwards-compatible; existing narrow URLs still work.
- APIs for fetching messages now provide more metadata to help clients.
- Clarified instructions for server settings (especially LDAP auth).
- Redesigned the "uploaded files" UI.
- Redesigned the "account settings" UI.
- Redesigned error pages for the various email confirmation flows.
- Added missing information on requesting user in many exception emails.
- Our emoji now display at full resolution on retina displays.
- Improved placement of text when inserting emoji via picker.
- Improved the password reset flow to be less confusing if you don't
have an account.
- Improved syntax for permanent links to streams in Zulip.
- Improved behavior of copy-pasting a large number of messages.
- Improved Tornado retry logic for connecting to RabbitMQ.
- Improved the descriptions and UI for many settings.
- Improved handling of browser undo in compose.
- Improved mobile notifications to support narrowing when one click a
mobile push notification.
- Improved visual design of the help center (/help/).
- Improved saved drafts system to garbage-collect old drafts and sort
by last modification, not creation.
- Removed the legacy "Zulip labs" autoscroll_forever setting. It was
enabled mostly by accident.
- Removed some long-deprecated markdown syntax for mentions.
- Added support for clicking on a mention to see a user's profile.
- Links to logged-in content in Zulip now take the user to the
appropriate upload or view after a user logs in.
- Renamed "Home" to "All messages", to avoid users clicking on it too
early in using Zulip.
- Added a user setting to control whether the organization's name is
included in email subject lines.
- Fixed uploading user avatars encoded using the CMYK mode.
**User accounts and invites:**
- Added support for users in multiple realms having the same email.
- Added a display for whether the user is logged-in in logged-out
pages.
- Added support for inviting a new user as an administrator.
- Added a new organization settings page for managing invites.
- Added rate-limiting on inviting users to join a realm (prevents spam).
- Added an organization setting to disable welcome emails to new users.
- Added an organization setting to ban disposable email addresses
(I.e.. those from sites like mailinator.com).
- Improved the password reset flow to be less confusing if you don't
have an account.
- Split the Notifications Stream setting in two settings, one for new
users, the other for new streams.
**Stream subscriptions and settings:**
- Added traffic statistics (messages/week) to the "Manage streams" UI.
- Fixed numerous issues in the "stream settings" UI.
- Fixed numerous subtle bugs with the stream creation UI.
- Changes the URL scheme for stream narrows to encode the stream ID,
so that they can be robust to streams being renamed. The change is
backwards-compatible; existing narrow URLs still work.
**API, bots, and integrations:**
- Rewrote our API documentation to be much more friendly and
expansive; it now covers most important endpoints, with nice examples.
- New integrations: ErrBot, GoCD, Google Code-In, Opbeat, Groove,
Raygun, Insping, Dialogflow, Dropbox, Front, Intercom,
Statuspage.io, Flock and Beeminder.
- Added support for embedded interactive bots.
- Added inline preview + player for Vimeo videos.
- Added new event types and fixed bugs in several webhook integrations.
- Added support for default bots to receive messages when they're
mentioned, even if they are not subscribed.
- Added support for overriding the topic is all incoming webhook integrations.
- Incoming webhooks now send a private message to the bot owner for
more convenient testing if a stream is not specified.
- Rewrote documentation for many integrations to use a cleaner
numbered-list format.
- APIs for fetching messages now provide more metadata to help clients.
**Keyboard shortcuts:**
- Added new "basics" section to keyboard shortcuts documentation.
- Added a new ">" keyboard shortcut for quote-and-reply.
- Added a new "p" keyboard shortcut to just to next unread PM thread.
- Fixed several hotkeys scope bugs.
- Changed the hotkey for compose-private-message from "C" to "x".
- Improve keyboard navigation of left and right sidebars with arrow keys.
**Mobile apps backend:**
- Added support for logging into the mobile apps with RemoteUserBackend.
- Improved mobile notifications to support narrowing when one clicks a
mobile push notification.
- Statistics on the fraction of strings that are translated now
include strings in the mobile apps as well.
**For server admins:**
- Added certbot support to the installer for getting certificates.
- Added support for hosting multiple domains, not all as subdomains of
the same base domain.
- Added a new nagios check for the Zulip analytics state.
- Fixed buggy APNs logic that could cause extra exception emails.
- Fixed a missing dependency for the localhost_sso auth backend.
- Fixed subtle bugs in garbage-collection of old node_modules versions.
- Clarified instructions for server settings (especially LDAP auth).
- Added missing information on requesting user in many exception emails.
- Improved Tornado retry logic for connecting to RabbitMQ.
- Added a server setting to control whether digest emails are sent.
**For Zulip developers:**
- Migrated the codebase to use the nice Python 3 typing syntax.
- Added a new /team/ page explaining the team, with a nice
visualization of our contributors.
- Dramatically improved organization of developer docs.
- Backend test coverage is now 95%.
- Countless other little bug fixes both in the backend and the UI.
### 1.7.2 -- 2018-04-12
This is a security release, with a handful of cherry-picked changes
since 1.7.1. All Zulip server admins are encouraged to upgrade
promptly.
- CVE-2018-9986: Fix XSS issues with frontend markdown processor.
- CVE-2018-9987: Fix XSS issue with muting notifications.
- CVE-2018-9990: Fix XSS issue with stream names in topic typeahead.
- CVE-2018-9999: Fix XSS issue with user uploads. The fix for this
adds a Content-Security-Policy for the `LOCAL_UPLOADS_DIR` storage
backend for user-uploaded files.
Thanks to Suhas Sunil Gaikwad for reporting CVE-2018-9987 and w2w for
reporting CVE-2018-9986 and CVE-2018-9990.
### 1.7.1 -- 2017-11-21

View File

@@ -61,7 +61,7 @@ contributors to the project today).
### Expectations for GSoC students
[Our guide for having a great summer with Zulip](../contributing/summer-with-zulip)
[Our guide for having a great summer with Zulip](../contributing/summer-with-zulip.html)
is focused on what one should know once doing a summer project with
Zulip. But it has a lot of useful advice on how we expect students to
interact, above and beyond what is discussed in Google's materials.

View File

@@ -1,30 +1,58 @@
# Outgoing email
This page documents everything you need to know about setting up
outgoing email in a Zulip production environment. It's pretty simple
if you already have an outgoing SMTP provider; just start reading from
[the configuration section](#configuration).
Zulip needs to be able to send email so it can confirm new users'
email addresses and send notifications.
## How to configure
1. Identify an outgoing email (SMTP) account where you can have Zulip
send mail. If you don't already have one you want to use, see
[Email services](#email-services) below.
2. Fill out the section of `/etc/zulip/settings.py` headed "Outgoing
email (SMTP) settings". This includes the hostname and typically
the port to reach your SMTP provider, and the username to log into
it as.
3. Put the password for the SMTP user account in
`/etc/zulip/zulip-secrets.conf` by setting `email_password`. For
example: `email_password = abcd1234`.
Like any other change to the Zulip configuration, be sure to
[restart the server](settings.html) to make your changes take
effect.
4. Test that your configuration is working. See the test command in
the [Troubleshooting](#troubleshooting) section below. If it's not
working, see the suggestions in that section.
## Email services
### Free outgoing email services
For sending outgoing email from your Zulip server, we highly recommend
using a "transactional email" service like
[Mailgun](https://documentation.mailgun.com/en/latest/quickstart-sending.html#send-via-smtp)
or for AWS users,
[SendGrid](https://sendgrid.com/docs/API_Reference/SMTP_API/integrating_with_the_smtp_api.html),
[Mailgun](https://documentation.mailgun.com/en/latest/quickstart-sending.html#send-via-smtp),
or, for AWS users,
[Amazon SES](http://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-smtp.html).
These services are designed to send email from servers, and are by far
the easiest way to get outgoing email working reliably.
If you don't have an existing outgoing SMTP provider, don't worry!
Both of the options we recommend above (as well as dozens of other
services) have free options; we recommend Mailgun as the easiest to
get setup with. Once you've signed up, you'll want to find the
service's provided "SMTP credentials", and configure Zulip as follows:
Each of the options we recommend above (as well as dozens of other
services) have free options. Once you've signed up, you'll want to
find the service's provided "SMTP credentials", and configure Zulip as
follows:
* The hostname as `EMAIL_HOST = 'smtp.mailgun.org'` in `/etc/zulip/settings.py`
* The username as `EMAIL_HOST_USER = 'username@example.com` in
* The hostname like `EMAIL_HOST = 'smtp.mailgun.org'` in `/etc/zulip/settings.py`
* The username like `EMAIL_HOST_USER = 'username@example.com` in
`/etc/zulip/settings.py`.
* The password as `email_password = abcd1234` in `/etc/zulip/zulip-secrets.conf`.
* The TLS setting as `EMAIL_USE_TLS = True` in
`/etc/zulip/settings.py`, for most providers
* The port as `EMAIL_PORT = 587` in `/etc/zulip/settings.py`, for most
providers
* The password like `email_password = abcd1234` in `/etc/zulip/zulip-secrets.conf`.
### Using Gmail for outgoing email
@@ -44,38 +72,28 @@ how to make it work:
* Note also that the rate limits for Gmail are also quite low
(e.g. 100 / day), so it's easy to get rate-limited if your server
has significant traffic. For more active servers, we recommend
moving to a free account from a transaction email service.
moving to a free account on a transactional email service.
### Logging outgoing email to a file for prototyping
If for prototyping, you don't want to bother setting up an email
provider, you can add to `/etc/zulip/settings.py` the following:
For prototyping, you might want to proceed without setting up an email
provider. If you want to see the emails Zulip would have sent, you
can log them to a file instead.
To do so, add these lines to `/etc/zulip/settings.py`:
```
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/var/log/zulip/emails'
```
Outgoing emails that Zulip would have sent will just be written to
files in `/var/log/zulip/emails/`. This is enough to get you through
initial user registration without an SMTP provider.
Then outgoing emails that Zulip would have sent will just be written
to files in `/var/log/zulip/emails/`.
Remember to delete this configuration and restart the server if you
later setup a real SMTP provider!
Remember to delete this configuration (and restart the server) if you
later set up a real SMTP provider!
### Configuration
To configure outgoing SMTP, you will need to complete the following steps:
1. Fill out the outgoing email sending configuration block in
`/etc/zulip/settings.py`, including `EMAIL_HOST`, and
`EMAIL_HOST_USER`. You may also need to set `EMAIL_PORT` if your
provider doesn't use the standard SMTP submission port (587).
2. Put the SMTP password for `EMAIL_HOST_USER` in
`/etc/zulip/zulip-secrets.conf` as `email_password = yourPassword`.
#### Testing and troubleshooting
## Troubleshooting
You can quickly test your outgoing email configuration using:
@@ -87,42 +105,50 @@ su zulip
If it doesn't throw an error, it probably worked; you can confirm by
checking your email.
It's important to test, because outgoing email often doesn't work the
first time. Common causes of failures are:
If it doesn't work, check these common failure causes:
* Your hosting provider blocking outgoing SMTP traffic in its
default firewall rules. Check whether `EMAIL_PORT` is blocked in your
hosting provider's firewall.
* Forgetting to put the password in `/etc/zulip/zulip-secrets.conf`.
* Typos in transcribing the username or password.
* Your hosting provider may block outgoing SMTP traffic in its default
firewall rules. Check whether the port `EMAIL_PORT` is blocked in
your hosting provider's firewall.
Once you have it working from the management command, remember to
restart your Zulip server using
`/home/zulip/deployments/current/scripts/restart-server` so that the running
server is using the latest configuration.
* Make sure you set the password in `/etc/zulip/zulip-secrets.conf`.
#### Advanced troubleshooting
* Check the username and password for typos.
* Be sure to restart your Zulip server after editing either
`settings.py` or `zulip-secrets.conf`, using
`/home/zulip/deployments/current/scripts/restart-server` .
Note that the `manage.py` command above will read the latest
configuration from the config files, even if the server is still
running with an old configuration.
### Advanced troubleshooting
Here are a few final notes on what to look at when debugging why you
aren't receiving emails from Zulip:
* Most transactional email services have an "outgoing email" log where
you can inspect the emails that reached the service, whether it was
flagged as spam, etc.
you can inspect the emails that reached the service, whether an
email was flagged as spam, etc.
* Starting with Zulip 1.7, Zulip logs an entry in
`/var/log/zulip/send_email.log` whenever it attempts to send an
email, including whether the request succeeded or failed.
email. The log entry includes whether the request succeeded or failed.
* If attempting to send an email throws an exception, a traceback
should be in `/var/log/zulip/errors.log`, along with any other
exceptions Zulip encounters.
* Zulip's email sending configuration is based on the standard Django
[SMTP backend](https://docs.djangoproject.com/en/1.10/topics/email/#smtp-backend)
[SMTP backend](https://docs.djangoproject.com/en/2.0/topics/email/#smtp-backend)
configuration. So if you're having trouble getting your email
provider working, you may want to search for documentation related
to using your email provider with Django. The one thing we've
changed from the defaults is reading the email password from the
`email_password` entry in the Zulip secrets file, as part of our
policy of not having any secret information in the
`/etc/zulip/settings.py` file. In other words, if Django
documentation references setting `EMAIL_HOST_PASSWORD`, you should
instead set `email_password` in `/etc/zulip/zulip-secrets.conf`.
to using your email provider with Django.
The one thing we've changed from the Django defaults is that we read
the email password from the `email_password` entry in the Zulip
secrets file, as part of our policy of not having any secret
information in the `/etc/zulip/settings.py` file. In other words,
if Django documentation references setting `EMAIL_HOST_PASSWORD`,
you should instead set `email_password` in
`/etc/zulip/zulip-secrets.conf`.

View File

@@ -16,7 +16,7 @@ recommended, and you may break your server. Make sure you have backups
and a provisioning script ready to go to wipe and restore your
existing services if (when) your server goes down.
These instructions are only for experts. If you're not an experiecned
These instructions are only for experts. If you're not an experienced
Linux sysadmin, you will have a much better experience if you get a
dedicated VM to install Zulip on instead (or [use zulipchat.com](https://zulipchat.com).

View File

@@ -13,10 +13,14 @@ If you already have an SSL certificate, just install (or symlink) its
files into place at the following paths:
* `/etc/ssl/private/zulip.key` for the private key
* `/etc/ssl/certs/zulip.combined-chain.crt` for the certificate.
Because Zulip uses nginx as its web server, this should be in the
format of a [chained certificate bundle][nginx-https].
[nginx-https]: http://nginx.org/en/docs/http/configuring_https_servers.html
Your certificate file should contain not only your own certificate but
its full chain, including any intermediate certificates used by your
CA. See the [nginx documentation][nginx-chains] for details on what
this means and how to do it and test it. If you're missing part of
the chain, your server may work with some browsers but not others.
[nginx-chains]: http://nginx.org/en/docs/http/configuring_https_servers.html#chains
## Certbot (recommended)

View File

@@ -36,5 +36,6 @@ Subsystems Documentation
documentation
conversion
input-pills
presence
unread_messages
user-docs

View File

@@ -0,0 +1,55 @@
# Presence
This document explains the model for Zulip's presence.
In a chat tool like Zulip, users expect to see the “presence” status
of other users: is the person I want to talk to currently online? If
not, were they last online 5 minutes ago, or more like an hour ago, or
a week? Presence helps set expectations for whether someone is likely
to respond soon. To a user, this feature can seem like a simple thing
that should be easy. But presence is actually one of the hardest
scalability problems for a team chat tool like Zulip.
There's a lot of performance-related details in the backend and
network protocol design that we won't get into here. The focus of
this is what one needs to know to correctly implement a Zulip client's
presence implementation (e.g. webapp, mobile app, terminal client, or
other tool that's intended to represent whether a user is online and
using Zulip).
A client should report to the server every minute a `POST` request to
`/users/me/presence`, containing the current user's status. The
requests contains a few parameters. The most important is "status",
which had 2 valid values:
* "active" -- this means the user has interacted with the client
recently. We use this for the "green" state in the webapp.
* "idle" -- the user has not interacted with the client recently.
This is important for the case where a user left a Zulip tab open on
their desktop at work and went home for the weekend. We use this
for the "orange" state in the webapp.
The client receives in the response to that request a data set that,
for each user, contains their status and timestamp that we last heard
from that client. There are a few important details to understand
about that data structure:
* It's really important that the timestamp is the last time we heard
from the client. A client can only interpret the status to display
about another user by doing a simple computation using the (status,
timestamp) pair. E.g. a user who last used Zulip 1 week ago will
have a timestamp of 1 week ago and a status of "active". Why?
Because this correctly handles the race conditions. For example, if
the threshhold for displaying a user as "offline" was 5 minutes
since the user was last online, the client can at any time
accurately compute whether that user is offline (even if the last
data from the server was 45 seconds ago, and the user was last
online 4:30 before the client received that server data).
* The `status_from_timestamp` function in `static/js/presence.js` is
useful sample code; the `OFFLINE_THRESHOLD_SECS` check is critical
to correct output.
* We provide the data for e.g. whether the user was online on their
desktop or the mobile app, but for a basic client, you will likely
only want to parse the "aggregated" key, which shows the summary
answer for "is this user online".

View File

@@ -249,9 +249,9 @@ always create a new macro by adding a new file to that folder.
### **Organization settings** `{!admin.md!}` macro
* **About:** Links to the **Organization settings** documentation.
Usually preceded by the [**Go to the** macro](#go-to-the-go-to-the-md-macro)
and a link to a particular section on the **Organization settings** page.
* **About:** Links to the **Organization settings** documentation. Usually
preceded by a link to a particular section on the **Organization settings**
page.
* **Contents:**
```md
@@ -260,7 +260,7 @@ and a link to a particular section on the **Organization settings** page.
* **Example usage and rendering:**
```md
{!go-to-the.md!} [Organization settings](/#organization/organization-settings)
1. Go to the [Organization settings](/#organization/organization-settings)
{!admin.md!}
```
```md
@@ -284,7 +284,7 @@ immediately after the title.
```md
{!admin-only.md!}
{!follow-steps.md!} change who can join your stream by changing the stream's
Follow the following steps to change who can join your stream by changing the stream's
accessibility.
```
```md
@@ -348,27 +348,6 @@ macro](#message-actions-message-actions-md-macro).
down chevron (<i class="fa fa-chevron-down"></i>) icon to reveal an actions dropdown.
```
### **Go to the** `{!go-to-the.md}` macro
* **About:** Usually precedes the [**Settings** macro](#settings-settings-md-macro)
or the [**Organization settings** macro](#organization-settings-admin-md-macro). Transforms
following content into a step.
* **Contents:**
```md
1. Go to the
```
* **Example usage and rendering:**
```md
{!go-to-the.md!} [Notifications](/#settings/notifications)
{!settings.md!}
```
```md
1. Go to the [Notifications](/#settings/notifications) tab on the
[Settings](/help/edit-settings) page.
```
### **Filter streams** `{!filter-streams.md!}` macro
* **About:** Explains how to search for specific streams in the
@@ -392,24 +371,6 @@ following content into a step.
name of the stream in the **Filter streams** input.
```
### **Follow steps** `{!follow-steps.md!}` macro
* **About:** Prepends phrases with instructions to follow the following steps.
* **Contents:**
```md
Follow the following steps to
```
* **Example usage and rendering:**
```md
{!follow-steps.md!} change your mobile notification settings.
```
```md
Follow the following steps to change your mobile notification
settings.
```
### **Message actions** `{!message-actions.md!}` macro
* **About:** Explains how to view the actions of message. Usually followed by an instruction
@@ -456,8 +417,7 @@ describing the settings they modified.
### **Settings** `{!settings.md!}` macro
* **About:** Links to the **Edit Settings** documentation. Usually preceded by
the [**Go to the** macro](#go-to-the-go-to-the-md-macro) and a link to a
particular section on the **Settings** page.
a link to a particular section on the **Settings** page.
* **Contents:**
```md
@@ -466,7 +426,7 @@ particular section on the **Settings** page.
* **Example usage and rendering:**
```md
{!go-to-the.md!} [Notifications](/#settings/notifications)
1. Go to the [Notifications](/#settings/notifications)
{!settings.md!}
```
```md

View File

@@ -483,8 +483,9 @@ to be added to the admin page (and its value added to the data sent back
to server when a realm is updated) and the change event needs to be
handled on the client.
To add the checkbox to the admin page, modify the relevant template,
`static/templates/settings/organization-permissions-admin.handlebars`
To add the checkbox to the admin page, modify the relevant template in
`static/templates/settings/`, which can be
`organization-permissions-admin.handlebars` or `organization-settings-admin.handlebars`
(omitted here since it is relatively straightforward).
Then add the new form control in `static/js/admin.js`.
@@ -507,66 +508,81 @@ function _setup_page() {
The JavaScript code for organization settings and permissions can be found in
`static/js/settings_org.js`.
There is a front-end version of `property_types`, which reduces the code
needed on the front end for a new feature.
In frontend, we have split the `property_types` into three objects:
Add the new feature to the `property_types` object in `settings_org.js`.
The key should be the setting name and the value should be an object with
the following keys:
- `org_profile`: This contains properties for the "organization
profile" settings page.
* type
* checked_msg (what message the user sees when they enable the setting)
* unchecked_msg (what message the user sees when they disable the setting)
- `org_settings`: This contains properties for the "organization
settings" page. Settings belonging to this section generally
decide what features should be available to a user like deleting a
message, message edit history etc. Our `mandatory_topics` feature
belongs in this section.
- `org_permissions`: This contains properties for the "organization
permissions" section. These properties control security controls
like who can join the organization and whether normal users can
create streams or upload custom emoji.
Once you've determined wheter the new setting belongs, the next step
is to find the right subsection of that page to put the setting
in. For example in this case of `mandatory_topics` it will lie in
"Message feed" (`msg_feed`) subsection.
*If you're not sure in which section your feature belongs, it's is
better to discuss it in the [community](https://chat.zulip.org/)
before implementing it.*
When defining the property, you'll also need to specify the property
field type (i.e. whether it's a `bool`, `integer` or `text`).
``` diff
// static/js/settings_org.js
var property_types = {
settings: {
var org_settings = {
msg_editing: {
// ...
},
permissions: { // ...
msg_feed: {
// ...
+ mandatory_topics: {
+ type: 'bool',
+ checked_msg: i18n.t("Topics are required in messages to streams"),
+ unchecked_msg: i18n.t("Topics are not required in messages to streams"),
},
+ },
},
};
```
Additionally, any code needed to update the UI when the setting is changed
should be written in a function inside `settings_org.js`.
For example, when a realm description is updated, that value change should
occur in other windows where the description field is visible:
Note that some settings, like `realm_create_stream_permission`,
reuqire special treatment, because they don't match the common
pattern. We can't extract the property name and compare the value of
such input elements with those in `page_params`, so we have to
manually handle such situations in a couple key functions:
# static/js/settings_org.js
- `settings_org.get_property_value`: This processes the property name
when it doesn't match a corresponding key in `page_params`, and
returns the current value of that property, which we can use to
compare and set the values of corresponding DOM element.
exports.update_realm_description = function () {
if (!meta.loaded) {
return;
}
$('#id_realm_description').val(page_params.realm_description);
};
This ensures the appropriate code will run even if the
changes are made in another browser window.
In the example of updating a `mandatory_topics` setting, most of the changes
are on the backend, so no UI updates are required.
- `settings_org.update_dependent_subsettings`: This handles settings
whose value and state depend on other elements. For example,
`realm_waiting_period_threshold` is only shown for with the right
state of `realm_create_stream_permission`.
Finally, update `server_events_dispatch.js` to handle related events coming from
the server. There is an object, `realm_settings`, in the function
`dispatch_normal_event`. The keys in this object are setting names and the
values are the UI updating functions to run when an event has occurred.
If there is no relevant UI change to make, the value should be `noop`
(this is the case for `mandatory_topics`). However, if you had written
a function in `settings_org.js` to update UI, that function should
be the value in the `realm_settings` object.
If there is no relevant UI change to make other than in settings page
itself, the value should be `noop` (this is the case for
`mandatory_topics`, since this setting only has an effect on the
backend, so no UI updates are required.).
However, if you had written a function to update the UI after a given
setting has changed, your function should be referenced in the
`realm_settings` of `server_events_dispatch.js`. See for example
`settings_emoji.update_custom_emoji_ui`.
``` diff
@@ -585,10 +601,35 @@ function dispatch_normal_event(event) {
};
```
Checkboxes and other common input elements handle the UI updates
automatically through the logic in `settings_org.sync_realm_settings`.
The rest of the `dispatch_normal_events` function updates the state of the
application if an update event has occurred on a realm property and runs
the associated function to update the application's UI, if necessary.
Here are few important cases you should consider when testing your changes:
- For organization settings where we have a "save/discard" model, make
sure both the "Save" and "Discard changes" buttons are working
properly.
- If your setting is dependent on another setting, carefully check
that both are properly synchronized. For example, the input element
for `realm_waiting_period_threshold` is shown only when we have
selected the custom time limit option in the
`realm_create_stream_permission` dropdown.
- Do some manual testing for the real-time synchronization of input
elements across the browsers and just like "Discard changes" button,
check whether dependent settings are synchronized properly (this is
easy to do by opening two browser windows to the settings page, and
making changes in one while watching the other).
- Each subsection has independent "Save" and "Discard changes"
buttons, so changes and saving in one subsection shouldn't affect
the others.
### Front End Tests
A great next step is to write front end tests. There are two types of

View File

@@ -11,6 +11,7 @@
"zrequire": false
},
"rules": {
"no-sync": 0
"no-sync": 0,
"prefer-const": "error"
}
}

View File

@@ -302,7 +302,7 @@ casper.thenClick('a[data-code="en"]');
* Changing the language back to English so that subsequent tests pass.
*/
casper.waitUntilVisible('#language-settings-status a', function () {
casper.test.assertSelectorHasText('#language-settings-status', 'Saved. Please reload for the change to take effect.');
casper.test.assertSelectorHasText('#language-settings-status', 'Gespeichert. Bitte lade die Seite neu um die Änderungen zu aktivieren.');
});
casper.thenOpen("http://zulip.zulipdev.com:9981/");

View File

@@ -198,9 +198,8 @@ casper.then(function () {
});
casper.then(function () {
casper.waitUntilVisible('div#admin-profile-field-status', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status',
'Custom profile field added!');
casper.waitUntilVisible('#admin-profile-field-status img', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status', 'Saved');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_name', 'Teams');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short Text');
casper.click('.profile-field-row button.open-edit-form');
@@ -217,9 +216,8 @@ casper.then(function () {
});
casper.then(function () {
casper.waitUntilVisible('div#admin-profile-field-status', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status',
'Custom profile field updated!');
casper.waitUntilVisible('#admin-profile-field-status img', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status', 'Saved');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_name', 'team');
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short Text');
casper.click('.profile-field-row button.delete');
@@ -227,9 +225,8 @@ casper.then(function () {
});
casper.then(function () {
casper.waitUntilVisible('div#admin-profile-field-status', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status',
'Custom profile field deleted!');
casper.waitUntilVisible('#admin-profile-field-status img', function () {
casper.test.assertSelectorHasText('div#admin-profile-field-status', 'Saved');
});
});

View File

@@ -40,7 +40,7 @@ zrequire('activity');
zrequire('stream_list');
set_global('blueslip', {
log: function () {},
log: () => {},
});
set_global('popovers', {
@@ -58,52 +58,52 @@ set_global('stream_popover', {
set_global('reload', {
is_in_progress: function () {return false;},
is_in_progress: () => false,
});
set_global('resize', {
resize_page_components: function () {},
resize_page_components: () => {},
});
set_global('window', 'window-stub');
var me = {
const me = {
email: 'me@zulip.com',
user_id: 999,
full_name: 'Me Myself',
};
var alice = {
const alice = {
email: 'alice@zulip.com',
user_id: 1,
full_name: 'Alice Smith',
};
var fred = {
const fred = {
email: 'fred@zulip.com',
user_id: 2,
full_name: "Fred Flintstone",
};
var jill = {
const jill = {
email: 'jill@zulip.com',
user_id: 3,
full_name: 'Jill Hill',
};
var mark = {
const mark = {
email: 'mark@zulip.com',
user_id: 4,
full_name: 'Marky Mark',
};
var norbert = {
const norbert = {
email: 'norbert@zulip.com',
user_id: 5,
full_name: 'Norbert Oswald',
};
var zoe = {
const zoe = {
email: 'zoe@example.com',
user_id: 6,
full_name: 'Zoe Yang',
};
var people = global.people;
const people = global.people;
people.add_in_realm(alice);
people.add_in_realm(fred);
@@ -114,16 +114,16 @@ people.add_in_realm(zoe);
people.add_in_realm(me);
people.initialize_current_user(me.user_id);
compose_fade.update_faded_users = function () {};
compose_fade.update_faded_users = () => {};
var real_update_huddles = activity.update_huddles;
activity.update_huddles = function () {};
const real_update_huddles = activity.update_huddles;
activity.update_huddles = () => {};
global.compile_template('user_presence_row');
global.compile_template('user_presence_rows');
global.compile_template('group_pms');
var presence_info = {};
const presence_info = {};
presence_info[alice.user_id] = { status: 'inactive' };
presence_info[fred.user_id] = { status: 'active' };
presence_info[jill.user_id] = { status: 'active' };
@@ -149,7 +149,7 @@ presence.presence_info = presence_info;
}());
(function test_sort_users() {
var user_ids = [alice.user_id, fred.user_id, jill.user_id];
const user_ids = [alice.user_id, fred.user_id, jill.user_id];
activity._sort_users(user_ids);
@@ -162,15 +162,15 @@ presence.presence_info = presence_info;
(function test_process_loaded_messages() {
var huddle1 = 'jill@zulip.com,norbert@zulip.com';
var timestamp1 = 1382479029; // older
const huddle1 = 'jill@zulip.com,norbert@zulip.com';
const timestamp1 = 1382479029; // older
var huddle2 = 'alice@zulip.com,fred@zulip.com';
var timestamp2 = 1382479033; // newer
const huddle2 = 'alice@zulip.com,fred@zulip.com';
const timestamp2 = 1382479033; // newer
var old_timestamp = 1382479000;
const old_timestamp = 1382479000;
var messages = [
const messages = [
{
type: 'private',
display_recipient: [{id: jill.user_id}, {id: norbert.user_id}],
@@ -197,8 +197,8 @@ presence.presence_info = presence_info;
activity.process_loaded_messages(messages);
var user_ids_string1 = people.emails_strings_to_user_ids_string(huddle1);
var user_ids_string2 = people.emails_strings_to_user_ids_string(huddle2);
const user_ids_string1 = people.emails_strings_to_user_ids_string(huddle1);
const user_ids_string2 = people.emails_strings_to_user_ids_string(huddle2);
assert.deepEqual(activity.get_huddles(), [user_ids_string2, user_ids_string1]);
}());
@@ -249,7 +249,7 @@ presence.presence_info = presence_info;
var huddle = 'alice@zulip.com,fred@zulip.com,jill@zulip.com,mark@zulip.com';
huddle = people.emails_strings_to_user_ids_string(huddle);
var presence_info = {};
const presence_info = {};
presence_info[alice.user_id] = { status: 'active' };
presence_info[fred.user_id] = { status: 'idle' }; // counts as present
// jill not in list
@@ -272,9 +272,9 @@ presence.presence_info[me.user_id] = { status: activity.ACTIVE };
activity.set_user_list_filter();
var user_order = [fred.user_id, jill.user_id, norbert.user_id,
const user_order = [fred.user_id, jill.user_id, norbert.user_id,
zoe.user_id, alice.user_id, mark.user_id];
var user_count = 6;
const user_count = 6;
// Mock the jquery is func
$('.user-list-filter').is = function (sel) {
@@ -297,7 +297,7 @@ $('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
$('.user-list-filter').focus();
$('#user_presences li.user_sidebar_entry.narrow-filter');
var users = activity.build_user_sidebar();
const users = activity.build_user_sidebar();
assert.deepEqual(users, [{
name: 'Fred Flintstone',
href: '#narrow/pm-with/2-fred',
@@ -350,15 +350,15 @@ $('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
}());
(function test_PM_update_dom_counts() {
var value = $.create('alice-value');
var count = $.create('alice-count');
var pm_key = alice.user_id.toString();
var li = $("li.user_sidebar_entry[data-user-id='" + pm_key + "']");
const value = $.create('alice-value');
const count = $.create('alice-count');
const pm_key = alice.user_id.toString();
const li = $("li.user_sidebar_entry[data-user-id='" + pm_key + "']");
count.set_find_results('.value', value);
li.set_find_results('.count', count);
count.set_parent(li);
var counts = new Dict();
const counts = new Dict();
counts.set(pm_key, 5);
li.addClass('user_sidebar_entry');
@@ -374,16 +374,16 @@ $('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
}());
(function test_group_update_dom_counts() {
var value = $.create('alice-fred-value');
var count = $.create('alice-fred-count');
var pm_key = alice.user_id.toString() + "," + fred.user_id.toString();
var li_selector = "li.group-pms-sidebar-entry[data-user-ids='" + pm_key + "']";
var li = $(li_selector);
const value = $.create('alice-fred-value');
const count = $.create('alice-fred-count');
const pm_key = alice.user_id.toString() + "," + fred.user_id.toString();
const li_selector = "li.group-pms-sidebar-entry[data-user-ids='" + pm_key + "']";
const li = $(li_selector);
count.set_find_results('.value', value);
li.set_find_results('.count', count);
count.set_parent(li);
var counts = new Dict();
const counts = new Dict();
counts.set(pm_key, 5);
li.addClass('group-pms-sidebar-entry');
@@ -446,12 +446,12 @@ $('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
// Disable scrolling into place
stream_list.scroll_element_into_container = function () {};
// up
var e = {
const e = {
keyCode: 38,
stopPropagation: function () {},
preventDefault: function () {},
};
var keydown_handler = $('.user-list-filter').get_on_handler('keydown');
const keydown_handler = $('.user-list-filter').get_on_handler('keydown');
keydown_handler(e);
// Now the last element is selected
sel_index = user_count - 1;
@@ -459,20 +459,12 @@ $('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
sel_index = sel_index - 1;
// down
e = {
keyCode: 40,
stopPropagation: function () {},
preventDefault: function () {},
};
e.keyCode = 40;
keydown_handler(e);
sel_index = sel_index + 1;
keydown_handler(e);
e = {
keyCode: 13,
stopPropagation: function () {},
preventDefault: function () {},
};
e.keyCode = 13;
// Enter text and narrow users
$(".user-list-filter").expectOne().val('ali');
@@ -486,16 +478,16 @@ $('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
}());
(function test_focus_user_filter() {
var e = {
stopPropagation: function () {},
const e = {
stopPropagation: () => {},
};
var click_handler = $('.user-list-filter').get_on_handler('click');
click_handler(e);
}());
(function test_focusout_user_filter() {
var e = { };
var click_handler = $('.user-list-filter').get_on_handler('blur');
const e = { };
const click_handler = $('.user-list-filter').get_on_handler('blur');
click_handler(e);
}());
@@ -508,7 +500,7 @@ presence.presence_info[norbert.user_id] = { status: activity.ACTIVE };
presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
(function test_filter_user_ids() {
var user_filter = $('.user-list-filter');
const user_filter = $('.user-list-filter');
user_filter.val(''); // no search filter
activity.set_user_list_filter();
@@ -551,7 +543,7 @@ presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
}());
(function test_insert_one_user_into_empty_list() {
var alice_li = $.create('alice list item');
const alice_li = $.create('alice list item');
// These selectors are here to avoid some short-circuit logic.
$('#user_presences').set_find_results('[data-user-id="1"]', alice_li);
@@ -562,9 +554,7 @@ presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
};
$.stub_selector('#user_presences li', {
toArray: function () {
return [];
},
toArray: () => [],
});
activity.insert_user_into_list(alice.user_id);
assert(appended_html.indexOf('data-user-id="1"') > 0);
@@ -572,7 +562,7 @@ presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
}());
(function test_insert_fred_after_alice() {
var fred_li = $.create('fred list item');
const fred_li = $.create('fred list item');
// These selectors are here to avoid some short-circuit logic.
$('#user_presences').set_find_results('[data-user-id="2"]', fred_li);
@@ -601,7 +591,7 @@ presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
}());
(function test_insert_fred_before_jill() {
var fred_li = $.create('fred-li');
const fred_li = $.create('fred-li');
// These selectors are here to avoid some short-circuit logic.
$('#user_presences').set_find_results('[data-user-id="2"]', fred_li);
@@ -637,7 +627,7 @@ activity.set_user_list_filter();
// This test only tests that we do not explode when
// try to insert Fred into a list where he does not
// match the search filter.
var user_filter = $('.user-list-filter');
const user_filter = $('.user-list-filter');
user_filter.val('do-not-match-filter');
activity.insert_user_into_list(fred.user_id);
}());
@@ -716,28 +706,24 @@ $('.user-list-filter').parent = function () {
}());
(function test_update_huddles_and_redraw() {
var value = $.create('alice-fred-value');
var count = $.create('alice-fred-count');
var pm_key = alice.user_id.toString() + "," + fred.user_id.toString();
var li_selector = "li.group-pms-sidebar-entry[data-user-ids='" + pm_key + "']";
var li = $(li_selector);
const value = $.create('alice-fred-value');
const count = $.create('alice-fred-count');
const pm_key = alice.user_id.toString() + "," + fred.user_id.toString();
const li_selector = "li.group-pms-sidebar-entry[data-user-ids='" + pm_key + "']";
const li = $(li_selector);
count.set_find_results('.value', value);
li.set_find_results('.count', count);
count.set_parent(li);
var real_get_huddles = activity.get_huddles;
activity.get_huddles = function () {
return ['1,2'];
};
const real_get_huddles = activity.get_huddles;
activity.get_huddles = () => ['1,2'];
activity.update_huddles = real_update_huddles;
activity.redraw();
assert.equal($('#group-pm-list').hasClass('show'), false);
page_params.realm_presence_disabled = false;
activity.redraw();
assert.equal($('#group-pm-list').hasClass('show'), true);
activity.get_huddles = function () {
return [];
};
activity.get_huddles = () => [];
activity.redraw();
assert.equal($('#group-pm-list').hasClass('show'), false);
activity.get_huddles = real_get_huddles;
@@ -745,35 +731,33 @@ $('.user-list-filter').parent = function () {
}());
(function test_set_user_status() {
var server_time = 500;
var info = {
const server_time = 500;
const info = {
website: {
status: "active",
timestamp: server_time,
},
};
var alice_li = $.create('alice-li');
const alice_li = $.create('alice-li');
$('#user_presences').set_find_results('[data-user-id="1"]', alice_li);
$('#user_presences').append = function () {};
$.stub_selector('#user_presences li', {
toArray: function () {
return [];
},
toArray: () => [],
});
presence.presence_info[alice.user_id] = undefined;
activity.set_user_status(me.email, info, server_time);
assert.equal(presence.presence_info[alice.user_id], undefined);
activity.set_user_status(alice.email, info, server_time);
var expected = { status: 'active', mobile: false, last_active: 500 };
const expected = { status: 'active', mobile: false, last_active: 500 };
assert.deepEqual(presence.presence_info[alice.user_id], expected);
activity.set_user_status(alice.email, info, server_time);
blueslip.warn = function (msg) {
assert.equal(msg, 'unknown email: foo@bar.com');
};
blueslip.error = function () {};
blueslip.error = () => {};
activity.set_user_status('foo@bar.com', info, server_time);
}());
@@ -783,10 +767,8 @@ $('.user-list-filter').parent = function () {
func();
},
});
$(window).focus = function (func) {
func();
};
$(window).idle = function () {};
$(window).focus = func => func();
$(window).idle = () => {};
channel.post = function (payload) {
payload.success({});
@@ -808,9 +790,8 @@ $('.user-list-filter').parent = function () {
zephyr_mirror_active: false,
});
};
global.setInterval = function (func) {
func();
};
global.setInterval = (func) => func();
activity.initialize();
assert($('#zephyr-mirror-error').hasClass('show'));
assert(!activity.new_user_input);

View File

@@ -1,9 +1,12 @@
set_global('i18n', global.stub_i18n);
zrequire('keydown_util');
zrequire('components');
var LEFT_KEY = { which: 37 };
var RIGHT_KEY = { which: 39 };
var noop = function () {};
var LEFT_KEY = { which: 37, preventDefault: noop };
var RIGHT_KEY = { which: 39, preventDefault: noop };
(function test_basics() {
var keydown_f;
@@ -118,7 +121,10 @@ var RIGHT_KEY = { which: 39 };
}
});
var widget = components.toggle({
var callback_value;
var widget;
widget = components.toggle({
name: "info-overlay-toggle",
selected: 0,
values: [
@@ -129,6 +135,12 @@ var RIGHT_KEY = { which: 39 };
callback: function (name, key) {
assert.equal(callback_args, undefined);
callback_args = [name, key];
// The subs code tries to get a widget value in the middle of a
// callback, which can lead to obscure bugs.
if (widget) {
callback_value = widget.value();
}
},
});
@@ -162,6 +174,7 @@ var RIGHT_KEY = { which: 39 };
assert.equal(tabs[2].class, 'last selected');
assert.deepEqual(callback_args, ['translated: Search operators', 'search-operators']);
assert.equal(widget.value(), 'translated: Search operators');
assert.equal(widget.value(), callback_value);
// try to crash the key handler
keydown_f.call(tabs[focused_tab], RIGHT_KEY);

View File

@@ -43,6 +43,7 @@ set_global('notifications', {
notify_above_composebox: noop,
clear_compose_notifications: noop,
});
set_global('subs', {});
// Setting these up so that we can test that links to uploads within messages are
// automatically converted to server relative links.
@@ -101,8 +102,12 @@ people.add(bob);
sub.subscribed = false;
stream_data.add_sub('social', sub);
templates.render = function (template_name) {
assert.equal(template_name, 'compose_not_subscribed');
return 'compose_not_subscribed_stub';
};
assert(!compose.validate_stream_message_address_info('social'));
assert.equal($('#compose-error-msg').html(), "translated: <p>You're not subscribed to the stream <b>social</b>.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>");
assert.equal($('#compose-error-msg').html(), 'compose_not_subscribed_stub');
global.page_params.narrow_stream = false;
channel.post = function (payload) {
@@ -121,7 +126,7 @@ people.add(bob);
payload.success(payload.data);
};
assert(!compose.validate_stream_message_address_info('Frontend'));
assert.equal($('#compose-error-msg').html(), "translated: <p>You're not subscribed to the stream <b>Frontend</b>.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>");
assert.equal($('#compose-error-msg').html(), 'compose_not_subscribed_stub');
channel.post = function (payload) {
assert.equal(payload.data.stream, 'Frontend');
@@ -631,6 +636,8 @@ people.add(bob);
}());
}());
set_global('document', 'document-stub');
(function test_enter_with_preview_open() {
// Test sending a message with content.
compose_state.set_message_type('stream');
@@ -697,12 +704,10 @@ people.add(bob);
};
var compose_finished_event_checked = false;
$.stub_selector(document, {
trigger: function (e) {
assert.equal(e.name, 'compose_finished.zulip');
compose_finished_event_checked = true;
},
});
$(document).trigger = function (e) {
assert.equal(e.name, 'compose_finished.zulip');
compose_finished_event_checked = true;
};
var send_message_called = false;
compose.send_message = function () {
send_message_called = true;
@@ -1142,6 +1147,51 @@ function test_raw_file_drop(raw_drop_func) {
assert(!$("#compose_invite_users").visible());
}());
(function test_compose_not_subscribed_clicked() {
var handler = $("#compose-send-status")
.get_on_handler('click', '.sub_unsub_button');
var subscription = {
stream_id: 102,
name: 'test',
subscribed: false,
};
var compose_not_subscribed_called = false;
subs.sub_or_unsub = function () {
compose_not_subscribed_called = true;
};
setup_parents_and_mock_remove('compose-send-status',
'sub_unsub_button',
'.compose_not_subscribed');
handler(event);
assert(compose_not_subscribed_called);
stream_data.add_sub('test', subscription);
$('#stream').val('test');
$("#compose-send-status").show();
handler(event);
assert(!$("#compose-send-status").visible());
}());
(function test_compose_not_subscribed_close_clicked() {
var handler = $("#compose-send-status")
.get_on_handler('click', '#compose_not_subscribed_close');
setup_parents_and_mock_remove('compose_user_not_subscribed_close',
'compose_not_subscribed_close',
'.compose_not_subscribed');
$("#compose-send-status").show();
handler(event);
assert(!$("#compose-send-status").visible());
}());
event = {
preventDefault: noop,
};

View File

@@ -3,8 +3,7 @@ var return_false = function () { return false; };
var return_true = function () { return true; };
set_global('document', {
location: {
},
location: {}, // we need this to load compose.js
});
set_global('page_params', {
@@ -26,6 +25,8 @@ zrequire('util');
zrequire('compose_state');
zrequire('compose_actions');
set_global('document', 'document-stub');
var start = compose_actions.start;
var cancel = compose_actions.cancel;
var get_focus_area = compose_actions._get_focus_area;
@@ -71,7 +72,7 @@ set_global('narrow_state', {
});
set_global('unread_ops', {
mark_message_as_read: noop,
notify_server_message_read: noop,
});
set_global('common', {

View File

@@ -26,11 +26,11 @@ var bob = {
full_name: 'Bob',
};
people.add(me);
people.add_in_realm(me);
people.initialize_current_user(me.user_id);
people.add(alice);
people.add(bob);
people.add_in_realm(alice);
people.add_in_realm(bob);
(function test_set_focused_recipient() {
@@ -65,6 +65,7 @@ people.add(bob);
assert(compose_fade.would_receive_message('me@example.com'));
assert(compose_fade.would_receive_message('alice@example.com'));
assert(!compose_fade.would_receive_message('bob@example.com'));
assert.equal(compose_fade.would_receive_message('nonrealmuser@example.com'), undefined);
var good_msg = {
type: 'stream',

View File

@@ -0,0 +1,144 @@
zrequire('compose_pm_pill');
zrequire('input_pill');
zrequire('user_pill');
set_global('$', global.make_zjquery());
set_global('people', {});
var pills = {
pill: {},
};
(function test_pills() {
var othello = {
user_id: 1,
email: 'othello@example.com',
full_name: 'Othello',
};
var iago = {
email: 'iago@zulip.com',
user_id: 2,
full_name: 'Iago',
};
var hamlet = {
email: 'hamlet@example.com',
user_id: 3,
full_name: 'Hamlet',
};
people.get_realm_persons = function () {
return [iago, othello, hamlet];
};
var recipient_stub = $("#private_message_recipient");
var pill_container_stub = $('.pill-container[data-before="You and"]');
recipient_stub.set_parent(pill_container_stub);
var create_item_handler;
var all_pills = {};
pills.appendValidatedData = function (item) {
var id = item.user_id;
assert.equal(all_pills[id], undefined);
all_pills[id] = item;
};
pills.items = function () {
return _.values(all_pills);
};
var text_cleared;
pills.clear_text = function () {
text_cleared = true;
};
var pills_cleared;
pills.clear = function () {
pills_cleared = true;
pills = {
pill: {},
};
all_pills= {};
};
var appendValue_called;
pills.appendValue = function (value) {
appendValue_called = true;
assert.equal(value, 'othello@example.com');
this.appendValidatedData(othello);
};
var get_by_email_called = false;
people.get_by_email = function (user_email) {
get_by_email_called = true;
if (user_email === iago.email) {
return iago;
}
if (user_email === othello.email) {
return othello;
}
};
var get_person_from_user_id_called = false;
people.get_person_from_user_id = function (id) {
get_person_from_user_id_called = true;
if (id === othello.user_id) {
return othello;
}
assert.equal(id, 3);
return hamlet;
};
function test_create_item(handler) {
(function test_rejection_path() {
var item = handler(othello.email, pills.items());
assert(get_by_email_called);
assert.equal(item, undefined);
}());
(function test_success_path() {
get_by_email_called = false;
var res = handler(iago.email, pills.items());
assert(get_by_email_called);
assert.equal(typeof(res), 'object');
assert.equal(res.user_id, iago.user_id);
assert.equal(res.display_value, iago.full_name);
}());
}
function input_pill_stub(opts) {
assert.equal(opts.container, pill_container_stub);
create_item_handler = opts.create_item_from_text;
assert(create_item_handler);
return pills;
}
set_global('input_pill', {
create: input_pill_stub,
});
compose_pm_pill.initialize();
assert(compose_pm_pill.my_pill);
compose_pm_pill.set_from_typeahead(othello);
compose_pm_pill.set_from_typeahead(hamlet);
var user_ids = compose_pm_pill.get_user_ids();
assert.deepEqual(user_ids, [othello.user_id, hamlet.user_id]);
var emails = compose_pm_pill.get_emails();
assert.equal(emails, 'othello@example.com,hamlet@example.com');
var items = compose_pm_pill.get_typeahead_items();
assert.deepEqual(items, [{email: 'iago@zulip.com', user_id: 2, full_name: 'Iago'}]);
test_create_item(create_item_handler);
compose_pm_pill.set_from_emails('othello@example.com');
assert(compose_pm_pill.my_pill);
assert(get_person_from_user_id_called);
assert(pills_cleared);
assert(appendValue_called);
assert(text_cleared);
}());

View File

@@ -1,3 +1,4 @@
set_global('i18n', global.stub_i18n);
zrequire('compose_state');
zrequire('ui_util');
zrequire('pm_conversations');
@@ -11,6 +12,9 @@ zrequire('stream_data');
zrequire('user_pill');
zrequire('compose_pm_pill');
zrequire('composebox_typeahead');
set_global('md5', function (s) {
return 'md5-' + s;
});
var ct = composebox_typeahead;
var noop = function () {};
@@ -446,17 +450,17 @@ user_pill.get_user_ids = function () {
// corresponding parts in bold.
options.query = 'oth';
actual_value = options.highlighter(othello);
expected_value = '<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-othello@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
options.query = 'Lear';
actual_value = options.highlighter(cordelia);
expected_value = '<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-cordelia@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
options.query = 'othello@zulip.com, co';
actual_value = options.highlighter(cordelia);
expected_value = '<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-cordelia@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Cordelia Lear</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">cordelia@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
// options.matcher()
@@ -561,12 +565,12 @@ user_pill.get_user_ids = function () {
// content_highlighter.
fake_this = { completing: 'mention', token: 'othello' };
actual_value = options.highlighter.call(fake_this, othello);
expected_value = '<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
expected_value = ' <img class="typeahead-image" src="https://secure.gravatar.com/avatar/md5-othello@zulip.com?d&#x3D;identicon&amp;s&#x3D;50" />\n<strong>Othello, the Moor of Venice</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">othello@zulip.com</small>\n';
assert.equal(actual_value, expected_value);
fake_this = { completing: 'mention', token: 'hamletcharacters' };
actual_value = options.highlighter.call(fake_this, hamletcharacters);
expected_value = '<strong>hamletcharacters</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">Characters of Hamlet</small>\n';
expected_value = ' <i class="typeahead-image icon icon-vector-group"></i>\n<strong>hamletcharacters</strong>&nbsp;&nbsp;\n<small class="autocomplete_secondary">Characters of Hamlet</small>\n';
assert.equal(actual_value, expected_value);
// options.matcher()
@@ -892,20 +896,14 @@ user_pill.get_user_ids = function () {
assert.deepEqual(returned, reference);
}
var all_items = [
{
special_item_text: 'all (Notify everyone)',
email: 'all',
var all_items = _.map(['all', 'everyone', 'stream'], function (mention) {
return {
special_item_text: 'translated: ' + mention +" (Notify stream)",
email: mention,
pm_recipient_count: Infinity,
full_name: 'all',
},
{
special_item_text: 'everyone (Notify everyone)',
email: 'everyone',
pm_recipient_count: Infinity,
full_name: 'everyone',
},
];
full_name: mention,
};
});
var people_with_all = global.people.get_realm_persons().concat(all_items);
var all_mentions = people_with_all.concat(global.user_groups.get_realm_user_groups());
@@ -1090,20 +1088,14 @@ user_pill.get_user_ids = function () {
}());
(function test_typeahead_results() {
var all_items = [
{
special_item_text: 'all (Notify everyone)',
email: 'all',
var all_items = _.map(['all', 'everyone', 'stream'], function (mention) {
return {
special_item_text: 'translated: ' + mention +" (Notify stream)",
email: mention,
pm_recipient_count: Infinity,
full_name: 'all',
},
{
special_item_text: 'everyone (Notify everyone)',
email: 'everyone',
pm_recipient_count: Infinity,
full_name: 'everyone',
},
];
full_name: mention,
};
});
var people_with_all = global.people.get_realm_persons().concat(all_items);
var all_mentions = people_with_all.concat(global.user_groups.get_realm_user_groups());
var stream_list = [denmark_stream, sweden_stream, netherland_stream];

View File

@@ -1,5 +1,9 @@
global.stub_out_jquery();
set_global('page_params', {
development: true,
});
var jsdom = require("jsdom");
global.document = jsdom.jsdom('<!DOCTYPE html><p>Hello world</p>');
var window = jsdom.jsdom().defaultView;

View File

@@ -360,6 +360,7 @@ var event_fixtures = {
name: 'devel',
stream_id: 42,
subscribers: ['alice@example.com', 'bob@example.com'],
email_address: 'devel+0138515295f4@zulipdev.com:9991',
// etc.
},
],
@@ -749,6 +750,7 @@ with_overrides(function (override) {
event = event_fixtures.realm_user__remove;
global.with_stub(function (stub) {
override('people.deactivate', stub.f);
override('stream_data.remove_deactivated_user_from_all_streams', noop);
dispatch(event);
var args = stub.get_args('person');
assert_same(args.person, event.person);
@@ -801,15 +803,21 @@ with_overrides(function (override) {
});
var event = event_fixtures.subscription__add;
global.with_stub(function (stub) {
override('stream_data.get_sub_by_id', function (stream_id) {
return {stream_id: stream_id};
global.with_stub(function (subscription_stub) {
global.with_stub(function (stream_email_stub) {
override('stream_data.get_sub_by_id', function (stream_id) {
return {stream_id: stream_id};
});
override('stream_events.mark_subscribed', subscription_stub.f);
override('stream_data.update_stream_email_address', stream_email_stub.f);
dispatch(event);
var args = subscription_stub.get_args('sub', 'subscribers');
assert_same(args.sub.stream_id, event.subscriptions[0].stream_id);
assert_same(args.subscribers, event.subscriptions[0].subscribers);
args = stream_email_stub.get_args('sub', 'email_address');
assert_same(args.email_address, event.subscriptions[0].email_address);
assert_same(args.sub.stream_id, event.subscriptions[0].stream_id);
});
override('stream_events.mark_subscribed', stub.f);
dispatch(event);
var args = stub.get_args('sub', 'subscribers');
assert_same(args.sub.stream_id, event.subscriptions[0].stream_id);
assert_same(args.subscribers, event.subscriptions[0].subscribers);
});
event = event_fixtures.subscription__peer_add;
@@ -935,7 +943,7 @@ with_overrides(function (override) {
});
});
// mark_message_as_read requires message_store and these dependencies.
// notify_server_message_read requires message_store and these dependencies.
zrequire('unread_ops');
zrequire('unread');
zrequire('topic_data');

View File

@@ -2,6 +2,7 @@ zrequire('util');
zrequire('unread');
zrequire('stream_data');
zrequire('people');
zrequire('Handlebars', 'handlebars');
zrequire('Filter', 'js/filter');
set_global('page_params', {});
@@ -585,7 +586,7 @@ function make_sub(name, stream_id) {
{operator: 'stream', operand: 'devel'},
{operator: 'topic', operand: 'JS'},
];
string = 'stream devel > JS';
string = 'stream devel &gt; JS';
assert.equal(Filter.describe(narrow), string);
narrow = [

View File

@@ -76,6 +76,12 @@ people.add({
email: 'leo@zulip.com',
});
people.add({
full_name: 'Bobby <h1>Tables</h1>',
user_id: 103,
email: 'bobby@zulip.com',
});
people.initialize_current_user(cordelia.user_id);
var hamletcharacters = {
@@ -92,8 +98,16 @@ var backend = {
members: [],
};
var edgecase_group = {
name: "Bobby <h1>Tables</h1>",
id: 3,
description: "HTML Syntax to check for Markdown edge cases.",
members: [],
};
global.user_groups.add(hamletcharacters);
global.user_groups.add(backend);
global.user_groups.add(edgecase_group);
var stream_data = global.stream_data;
var denmark = {
@@ -111,8 +125,16 @@ var social = {
in_home_view: true,
invite_only: true,
};
var edgecase_stream = {
subscribed: true,
color: 'green',
name: 'Bobby <h1>Tables</h1>',
stream_id: 3,
in_home_view: true,
};
stream_data.add_sub('Denmark', denmark);
stream_data.add_sub('social', social);
stream_data.add_sub('Bobby <h1>Tables</h1>', edgecase_stream);
// Check the default behavior of fenced code blocks
// works properly before markdown is initialized.
@@ -305,6 +327,23 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
{input: ':)',
expected: '<p><span class="emoji emoji-1f603" title="smiley">:smiley:</span></p>',
translate_emoticons: true},
// Test HTML Escape in Custom Zulip Rules
{input: '@**<h1>The Rogue One</h1>**',
expected: '<p>@**&lt;h1&gt;The Rogue One&lt;/h1&gt;**</p>'},
{input: '#**<h1>The Rogue One</h1>**',
expected: '<p>#**&lt;h1&gt;The Rogue One&lt;/h1&gt;**</p>'},
{input: '!avatar(<h1>The Rogue One</h1>)',
expected: '<p><img alt="&lt;h1&gt;The Rogue One&lt;/h1&gt;" class="message_body_gravatar" src="/avatar/&lt;h1&gt;The Rogue One&lt;/h1&gt;?s=30" title="&lt;h1&gt;The Rogue One&lt;/h1&gt;"></p>'},
{input: ':<h1>The Rogue One</h1>:',
expected: '<p>:&lt;h1&gt;The Rogue One&lt;/h1&gt;:</p>'},
{input: '@**O\'Connell**',
expected: '<p>@**O&#39;Connell**</p>'},
{input: '@*Bobby <h1>Tables</h1>*',
expected: '<p><span class="user-group-mention" data-user-group-id="3">@Bobby &lt;h1&gt;Tables&lt;/h1&gt;</span></p>'},
{input: '@**Bobby <h1>Tables</h1>**',
expected: '<p><span class="user-mention" data-user-id="103">@Bobby &lt;h1&gt;Tables&lt;/h1&gt;</span></p>'},
{input: '#**Bobby <h1>Tables</h1>**',
expected: '<p><a class="stream" data-stream-id="3" href="http://zulip.zulipdev.com/#narrow/stream/3-Bobby-.3Ch1.3ETables.3C.2Fh1.3E">#Bobby &lt;h1&gt;Tables&lt;/h1&gt;</a></p>'},
];
// We remove one of the unicode emoji we put as input in one of the test
@@ -322,7 +361,6 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
var message = {raw_content: input};
markdown.apply_markdown(message);
var output = message.content;
assert.equal(expected, output);
});
}());
@@ -386,6 +424,20 @@ var bugdown_data = JSON.parse(fs.readFileSync(path.join(__dirname, '../../zerver
assert.equal(message.mentioned, true);
assert.equal(message.mentioned_me_directly, true);
input = "test @**everyone**";
message = {subject: "No links here", raw_content: input};
markdown.apply_markdown(message);
assert.equal(message.is_me_message, false);
assert.equal(message.mentioned, true);
assert.equal(message.mentioned_me_directly, false);
input = "test @**stream**";
message = {subject: "No links here", raw_content: input};
markdown.apply_markdown(message);
assert.equal(message.is_me_message, false);
assert.equal(message.mentioned, true);
assert.equal(message.mentioned_me_directly, false);
input = "test @all";
message = {subject: "No links here", raw_content: input};
markdown.apply_markdown(message);

View File

@@ -77,9 +77,12 @@ function set_filter(operators) {
var hide_id;
var show_id;
global.$ = function (id) {
return {hide: function () {hide_id = id;}, show: function () {show_id = id;}};
};
set_global('$', (id) => {
return {
hide: () => {hide_id = id;},
show: () => {show_id = id;},
};
});
narrow_state.reset_current_filter();
narrow.show_empty_narrow_message();

View File

@@ -1,7 +1,7 @@
// Dependencies
zrequire('muting');
zrequire('stream_data');
set_global('$', global.make_zjquery({
silent: true,
}));
set_global('document', {
hasFocus: function () {
return true;
@@ -12,6 +12,15 @@ set_global('page_params', {
is_admin: false,
realm_users: [],
});
// For people.js
set_global('md5', function (s) {
return 'md5-' + s;
});
zrequire('muting');
zrequire('stream_data');
zrequire('ui');
zrequire('people');
zrequire('notifications');
@@ -146,3 +155,108 @@ stream_data.add_sub('stream_two', two);
subject: 'topic_two',
}), true);
}());
(function test_basic_notifications() {
var n; // Object for storing all notification data for assertions.
var last_closed_message_id = null;
var last_shown_message_id = null;
// Notifications API stub
notifications.set_notification_api({
checkPermission: function checkPermission() {
if (window.Notification.permission === 'granted') {
return 0;
}
return 2;
},
requestPermission: function () {
return;
},
createNotification: function createNotification(icon, title, content, tag) {
var notification_object = {icon: icon, body: content, tag: tag};
// properties for testing.
notification_object.tests = {
shown: false,
};
notification_object.show = function () {
last_shown_message_id = this.tag;
};
notification_object.close = function () {
last_closed_message_id = this.tag;
};
notification_object.cancel = function () { notification_object.close(); };
return notification_object;
},
});
var message_1 = {
id: 1000,
content: '@-mentions the user',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_two',
};
var message_2 = {
id: 1500,
content: '@-mentions the user',
sent_by_me: false,
notification_sent: false,
mentioned_me_directly: true,
type: 'stream',
stream: 'stream_one',
stream_id: 10,
subject: 'topic_four',
};
// Send notification.
notifications.process_notification({message: message_1, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, true);
assert.equal(Object.keys(n).length, 1);
assert.equal(last_shown_message_id, message_1.id);
// Remove notification.
notifications.close_notification(message_1);
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, false);
assert.equal(Object.keys(n).length, 0);
assert.equal(last_closed_message_id, message_1.id);
// Send notification.
message_1.id = 1001;
notifications.process_notification({message: message_1, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, true);
assert.equal(Object.keys(n).length, 1);
assert.equal(last_shown_message_id, message_1.id);
// Process same message again. Notification count shouldn't increase.
message_1.id = 1002;
notifications.process_notification({message: message_1, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, true);
assert.equal(Object.keys(n).length, 1);
assert.equal(last_shown_message_id, message_1.id);
// Send another message. Notification count should increase.
notifications.process_notification({message: message_2, webkit_notify: true});
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_four' in n, true);
assert.equal(Object.keys(n).length, 2);
assert.equal(last_shown_message_id, message_2.id);
// Remove notifications.
notifications.close_notification(message_1);
notifications.close_notification(message_2);
n = notifications.get_notifications();
assert.equal('undefined to stream_one > topic_two' in n, false);
assert.equal(Object.keys(n).length, 0);
assert.equal(last_closed_message_id, message_2.id);
}());

View File

@@ -381,6 +381,8 @@ initialize();
people.add(charles);
people.add(maria);
assert.equal(people.small_avatar_url_for_person(maria),
'https://secure.gravatar.com/avatar/md5-athens@example.com?d=identicon&s=50');
var message = {
type: 'private',
display_recipient: [

View File

@@ -288,7 +288,7 @@ set_global('current_msg_list', {
reactions.set_reaction_count(reaction_element, 5);
assert.equal(count_element.html(), '5');
assert.equal(count_element.text(), '5');
}());
(function test_get_reaction_section() {
@@ -388,7 +388,7 @@ set_global('current_msg_list', {
reactions.add_reaction(bob_event);
assert(title_set);
assert.equal(count_element.html(), '2');
assert.equal(count_element.text(), '2');
// Now, remove Bob's 8ball emoji. The event has the same exact
// structure as the add event.
@@ -402,7 +402,7 @@ set_global('current_msg_list', {
reactions.remove_reaction(bob_event);
assert(title_set);
assert.equal(count_element.html(), '1');
assert.equal(count_element.text(), '1');
var current_emojis = reactions.get_emojis_used_by_user_for_message_id(1001);
assert.deepEqual(current_emojis, ['smile', 'inactive_realm_emoji', '8ball']);

View File

@@ -596,7 +596,7 @@ init();
return suggestions.lookup_table[q].description;
}
assert.equal(describe('te'), "Search for te");
assert.equal(describe('stream:office topic:team'), "Stream office > team");
assert.equal(describe('stream:office topic:team'), "Stream office &gt; team");
suggestions = search.get_suggestions('topic:staplers stream:office');
expected = [

View File

@@ -0,0 +1,56 @@
set_global('$', global.make_zjquery());
zrequire('settings_muting');
zrequire('muting');
set_global('muting_ui', {});
var noop = function () {};
(function test_settings() {
muting.add_muted_topic('frontend', 'js');
var set_up_ui_called = false;
muting_ui.set_up_muted_topics_ui = function (opts) {
assert.deepEqual(opts, [['frontend', 'js']]);
set_up_ui_called = true;
};
settings_muting.set_up();
var click_handler = $('body').get_on_handler('click', '.settings-unmute-topic');
assert.equal(typeof(click_handler), 'function');
var event = {
stopImmediatePropagation: noop,
};
var fake_this = $.create('fake.settings-unmute-topic');
var tr_html = $('tr[data-topic="js"]');
fake_this.closest = function (opts) {
assert.equal(opts, 'tr');
return tr_html;
};
var data_called = 0;
tr_html.data = function (opts) {
if (opts === 'stream') {
data_called += 1;
return 'frontend';
}
if (opts === 'topic') {
data_called += 1;
return 'js';
}
};
var unmute_called = false;
muting_ui.unmute = function (stream, topic) {
assert.equal(stream, 'frontend');
assert.equal(topic, 'js');
unmute_called = true;
};
click_handler.call(fake_this, event);
assert(unmute_called);
assert(set_up_ui_called);
assert.equal(data_called, 2);
}());

View File

@@ -5,6 +5,7 @@ zrequire('stream_data');
zrequire('settings_account');
zrequire('settings_org');
zrequire('settings_ui');
zrequire('settings_ui');
var noop = function () {};
@@ -136,12 +137,63 @@ function test_realms_domain_modal(add_realm_domain) {
error_callback({});
assert.equal(info.val(), 'translated: Failed');
}
function createSaveButtons() {
var stub_save_button_header = $('.subsection-header');
var save_btn_controls = $.create('.save-btn-controls');
var stub_save_button = $('#org-submit-msg-editing');
var stub_save_button_text = $.create('.icon-button-text');
stub_save_button_header.prevAll = function () {
return $.create('<stub failed alert status element>');
};
stub_save_button.closest = function () {
return stub_save_button_header;
};
save_btn_controls.set_find_results(
'.save-button', stub_save_button
);
stub_save_button.set_find_results(
'.icon-button-text', stub_save_button_text
);
stub_save_button_header.set_find_results(
'.save-button-controls', save_btn_controls
);
stub_save_button_header.set_find_results(
'.subsection-changes-discard .button', $.create('#org-discard-msg-editing')
);
var props = {};
props.hidden = false;
props.status = "";
stub_save_button.attr = function (name, val) {
if (name === "data-status") {
if (val !== null) {
props.status = val;
return;
}
return props.status;
} else if (name === "id") {
return 'org-submit-msg-editing';
}
};
save_btn_controls.animate = function (obj) {
if (obj.opacity === 0) {
props.hidden = true;
} else {
props.hidden = false;
}
};
return {
props: props,
save_button: stub_save_button,
save_button_header: stub_save_button_header,
save_button_controls: save_btn_controls,
save_button_text: stub_save_button_text,
};
}
function test_submit_settings_form(submit_form) {
var ev = {
preventDefault: noop,
stopPropagation: noop,
target: '#org-submit-msg-editing',
currentTarget: '#org-submit-msg-editing',
};
$('#id_realm_default_language').val('fr');
@@ -155,20 +207,7 @@ function test_submit_settings_form(submit_form) {
success_callback = req.success;
};
var stub_save_button = $('#org-submit-msg-editing');
stub_save_button.attr = function () {
return 'org-submit-msg-editing';
};
var stub_save_button_header = $('.subsection-header');
stub_save_button_header.prevAll = function () {
return $.create('<stub failed alert status element>');
};
stub_save_button.closest = function () {
return stub_save_button_header;
};
stub_save_button_header.set_find_results(
'.subsection-changes-discard button', $.create('#org-discard-msg-editing')
);
createSaveButtons();
submit_form(ev);
assert(patched);
@@ -177,7 +216,6 @@ function test_submit_settings_form(submit_form) {
allow_message_editing: true,
message_content_edit_limit_seconds: 210,
};
success_callback(response_data);
var updated_value_from_response = $('#id_realm_message_content_edit_limit_minutes').val();
@@ -195,6 +233,38 @@ function test_submit_settings_form(submit_form) {
assert(updated_value_from_response, 0);
}
function test_change_save_button_state() {
set_global('$', global.make_zjquery());
var stubs = createSaveButtons();
var $save_btn_controls = stubs.save_button_controls;
var $save_btn_text = stubs.save_button_text;
var $save_btn = stubs.save_button;
var props = stubs.props;
settings_org.change_save_button_state($save_btn_controls, "unsaved");
assert.equal($save_btn_text.text(), 'translated: Save changes');
assert.equal(props.hidden, false);
assert.equal(props.status, "unsaved");
settings_org.change_save_button_state($save_btn_controls, "saved");
assert.equal($save_btn_text.text(), 'translated: Save changes');
assert.equal(props.hidden, true);
assert.equal(props.status, "");
settings_org.change_save_button_state($save_btn_controls, "saving");
assert.equal($save_btn_text.text(), 'translated: Saving');
assert.equal(props.status, "saving");
assert.equal($save_btn.hasClass('saving'), true);
settings_org.change_save_button_state($save_btn_controls, "discarded");
assert.equal(props.hidden, true);
assert.equal($save_btn.hasClass('saving'), false);
settings_org.change_save_button_state($save_btn_controls, "succeeded");
assert.equal(props.hidden, true);
assert.equal(props.status, "saved");
assert.equal($save_btn_text.text(), 'translated: Saved');
settings_org.change_save_button_state($save_btn_controls, "failed");
assert.equal(props.hidden, false);
assert.equal(props.status, "failed");
assert.equal($save_btn_text.text(), 'translated: Save changes');
}
function test_upload_realm_icon(upload_realm_icon) {
var form_data = {
append: function (field, val) {
@@ -332,14 +402,14 @@ function test_change_allow_subdomains(change_allow_subdomains) {
domain_obj.text(domain);
var elem_obj = $('<elem html>');
var elem_obj = $.create('<elem html>');
var parents_obj = $.create('parents object');
elem_obj.set_parents_result('tr', parents_obj);
parents_obj.set_find_results('.domain', domain_obj);
elem_obj.prop('checked', allow);
change_allow_subdomains.apply('<elem html>', [ev]);
change_allow_subdomains.apply(elem_obj, [ev]);
success_callback();
assert.equal(info.val(),
@@ -350,7 +420,7 @@ function test_change_allow_subdomains(change_allow_subdomains) {
allow = false;
elem_obj.prop('checked', allow);
change_allow_subdomains.apply('<elem html>', [ev]);
change_allow_subdomains.apply(elem_obj, [ev]);
success_callback();
assert.equal(info.val(),
'translated: Update successful: Subdomains no longer allowed for example.com');
@@ -400,7 +470,7 @@ function test_extract_property_name() {
var submit_settings_form;
$('.organization').on = function (action, selector, f) {
if (selector === '.subsection-header .subsection-changes-save button') {
if (selector === '.subsection-header .subsection-changes-save .button') {
assert.equal(action, 'click');
submit_settings_form = f;
}
@@ -422,6 +492,8 @@ function test_extract_property_name() {
upload_realm_icon = f;
};
var stub_render_notifications_stream_ui = settings_org.render_notifications_stream_ui;
settings_org.render_notifications_stream_ui = noop;
var parent_elem = $.create('waiting-period-parent-stub');
$('#id_realm_waiting_period_threshold').set_parent(parent_elem);
// TEST set_up() here, but this mostly just allows us to
@@ -439,11 +511,17 @@ function test_extract_property_name() {
test_disable_signup_notifications_stream(callbacks.disable_signup_notifications_stream);
test_change_allow_subdomains(change_allow_subdomains);
test_extract_property_name();
settings_org.render_notifications_stream_ui = stub_render_notifications_stream_ui;
test_change_save_button_state();
}());
(function test_misc() {
page_params.is_admin = false;
var stub_notification_disable_parent = $.create('<stub notification_disable parent');
stub_notification_disable_parent.set_find_results('.notification-disable',
$.create('<disable link>'));
page_params.realm_name_changes_disabled = false;
settings_account.update_name_change_display();
assert.equal($('#full_name').attr('disabled'), false);
@@ -469,6 +547,9 @@ function test_extract_property_name() {
assert.equal($("#change_email .button").attr('disabled'), false);
var elem = $('#realm_notifications_stream_name');
elem.closest = function () {
return stub_notification_disable_parent;
};
stream_data.get_sub_by_id = function (stream_id) {
assert.equal(stream_id, 42);
return { name: 'some_stream' };
@@ -483,6 +564,9 @@ function test_extract_property_name() {
assert(elem.hasClass('text-warning'));
elem = $('#realm_signup_notifications_stream_name');
elem.closest = function () {
return stub_notification_disable_parent;
};
stream_data.get_sub_by_id = function (stream_id) {
assert.equal(stream_id, 75);
return { name: 'some_stream' };

View File

@@ -169,33 +169,34 @@ zrequire('marked', 'third/marked/lib/marked');
stream_data.set_subscribers(sub, [fred.user_id, george.user_id]);
stream_data.update_calculated_fields(sub);
assert(stream_data.user_is_subscribed('Rome', 'FRED@zulip.com'));
assert(stream_data.user_is_subscribed('Rome', 'fred@zulip.com'));
assert(stream_data.user_is_subscribed('Rome', 'george@zulip.com'));
assert(!stream_data.user_is_subscribed('Rome', 'not_fred@zulip.com'));
assert(stream_data.is_user_subscribed('Rome', fred.user_id));
assert(stream_data.is_user_subscribed('Rome', george.user_id));
assert(!stream_data.is_user_subscribed('Rome', not_fred.user_id));
stream_data.set_subscribers(sub, []);
var email = 'brutus@zulip.com';
var brutus = {
email: email,
email: 'brutus@zulip.com',
full_name: 'Brutus',
user_id: 104,
};
people.add(brutus);
assert(!stream_data.user_is_subscribed('Rome', email));
assert(!stream_data.is_user_subscribed('Rome', brutus.user_id));
// add
var ok = stream_data.add_subscriber('Rome', brutus.user_id);
assert(ok);
assert(stream_data.user_is_subscribed('Rome', email));
assert(stream_data.is_user_subscribed('Rome', brutus.user_id));
sub = stream_data.get_sub('Rome');
stream_data.update_subscribers_count(sub);
assert.equal(sub.subscriber_count, 1);
var sub_email = "Rome:214125235@zulipdev.com:9991";
stream_data.update_stream_email_address(sub, sub_email);
assert.equal(sub.email_address, sub_email);
// verify that adding an already-added subscriber is a noop
stream_data.add_subscriber('Rome', brutus.user_id);
assert(stream_data.user_is_subscribed('Rome', email));
assert(stream_data.is_user_subscribed('Rome', brutus.user_id));
sub = stream_data.get_sub('Rome');
stream_data.update_subscribers_count(sub);
assert.equal(sub.subscriber_count, 1);
@@ -203,20 +204,22 @@ zrequire('marked', 'third/marked/lib/marked');
// remove
ok = stream_data.remove_subscriber('Rome', brutus.user_id);
assert(ok);
assert(!stream_data.user_is_subscribed('Rome', email));
assert(!stream_data.is_user_subscribed('Rome', brutus.user_id));
sub = stream_data.get_sub('Rome');
stream_data.update_subscribers_count(sub);
assert.equal(sub.subscriber_count, 0);
// verify that checking subscription with bad email is a noop
var bad_email = 'notbrutus@zulip.org';
global.blueslip.error = function (msg) {
assert.equal(msg, "Unknown email for get_user_id: " + bad_email);
};
// verify that deactivating user should unsubscribe user from all streams
assert(stream_data.add_subscriber('Rome', george.user_id));
set_global('subs', { rerender_subscriptions_settings: function () {} });
stream_data.remove_deactivated_user_from_all_streams(george.user_id);
assert(!stream_data.is_user_subscribed('Rome', george.user_id));
// verify that checking subscription with undefined user id
global.blueslip.warn = function (msg) {
assert.equal(msg, "Bad email passed to user_is_subscribed: " + bad_email);
assert.equal(msg, "Undefined user_id passed to function is_user_subscribed");
};
assert(!stream_data.user_is_subscribed('Rome', bad_email));
assert.equal(stream_data.is_user_subscribed('Rome', undefined), undefined);
// Verify noop for bad stream when removing subscriber
var bad_stream = 'UNKNOWN';
@@ -233,7 +236,7 @@ zrequire('marked', 'third/marked/lib/marked');
// verify that removing an already-removed subscriber is a noop
ok = stream_data.remove_subscriber('Rome', brutus.user_id);
assert(!ok);
assert(!stream_data.user_is_subscribed('Rome', email));
assert(!stream_data.is_user_subscribed('Rome', brutus.user_id));
sub = stream_data.get_sub('Rome');
stream_data.update_subscribers_count(sub);
assert.equal(sub.subscriber_count, 0);
@@ -244,24 +247,24 @@ zrequire('marked', 'third/marked/lib/marked');
stream_data.add_sub('Rome', sub);
stream_data.add_subscriber('Rome', brutus.user_id);
sub.subscribed = true;
assert(stream_data.user_is_subscribed('Rome', email));
assert(stream_data.is_user_subscribed('Rome', brutus.user_id));
// Verify that we noop and don't crash when unsubscribed.
sub.subscribed = false;
stream_data.update_calculated_fields(sub);
ok = stream_data.add_subscriber('Rome', brutus.user_id);
assert(ok);
assert.equal(stream_data.user_is_subscribed('Rome', email), true);
assert.equal(stream_data.is_user_subscribed('Rome', brutus.user_id), true);
stream_data.remove_subscriber('Rome', brutus.user_id);
assert.equal(stream_data.user_is_subscribed('Rome', email), false);
assert.equal(stream_data.is_user_subscribed('Rome', brutus.user_id), false);
stream_data.add_subscriber('Rome', brutus.user_id);
assert.equal(stream_data.user_is_subscribed('Rome', email), true);
assert.equal(stream_data.is_user_subscribed('Rome', brutus.user_id), true);
sub.invite_only = true;
stream_data.update_calculated_fields(sub);
assert.equal(stream_data.user_is_subscribed('Rome', email), undefined);
assert.equal(stream_data.is_user_subscribed('Rome', brutus.user_id), undefined);
stream_data.remove_subscriber('Rome', brutus.user_id);
assert.equal(stream_data.user_is_subscribed('Rome', email), undefined);
assert.equal(stream_data.is_user_subscribed('Rome', brutus.user_id), undefined);
// Verify that we don't crash and return false for a bad stream.
ok = stream_data.add_subscriber('UNKNOWN', brutus.user_id);

View File

@@ -622,6 +622,13 @@ function render(template_name, args) {
assert.equal(error_msg, "translated: This stream is reserved for announcements.\n \n Are you sure you want to message all 101 people in this stream?");
}());
(function compose_not_subscribed() {
var html = render('compose_not_subscribed');
global.write_handlebars_output("compose_not_subscribed", html);
var button = $(html).find("button:first");
assert.equal(button.text(), "translated: Subscribe");
}());
(function compose_notification() {
var args = {
note: "You sent a message to a muted topic.",
@@ -1064,6 +1071,33 @@ function render(template_name, args) {
assert.equal(label.text().trim(), 'King Lear (lear@zulip.com)');
}());
(function non_editable_user_group() {
var args = {
user_group: {
id: "9",
name: "uranohoshi",
description: "Students at Uranohoshi Academy",
},
};
var html = '';
html += '<div id="user-groups">';
html += render('non_editable_user_group', args);
html += '</div>';
global.write_handlebars_output('non_editable_user_group', html);
var group_id = $(html).find('.user-group:first').prop('id');
var group_name_pills = $(html).find('.user-group:first .pill-container').attr('data-group-pills');
var group_name_display = $(html).find('.user-group:first .name').text().trim().replace(/\s+/g, ' ');
var group_description = $(html).find('.user-group:first .description').text().trim().replace(/\s+/g, ' ');
assert.equal(group_id, '9');
assert.equal(group_name_pills, 'uranohoshi');
assert.equal(group_name_display, 'uranohoshi');
assert.equal(group_description, 'Students at Uranohoshi Academy');
}());
(function notification() {
var args = {
content: "Hello",

View File

@@ -197,7 +197,7 @@ zrequire('timerender');
(function test_set_full_datetime() {
var message = {
timestamp: 1495091573, // 5/18/2017 7:12:53 AM (UTC+0)
timestamp: 1495091573, // 2017-5-18 07:12:53 AM (UTC+0)
};
var time_element = $('<span/>');
var attrs = new Dict();
@@ -207,7 +207,14 @@ zrequire('timerender');
return time_element;
};
var expected = '5/18/2017 7:12:53 AM (UTC+0)';
// since node >= 8 date.toLocaleDateString and
// date.toLocaleTimeString have been changed, instead of
// returning 5/18/2017 7:12:53 AM (UTC+0) they now return
// 2017-5-18 07:12:53 (UTC+0) - This change does not affect browsers
// since browsers have their own way of returning string that is
// or maybe inconsistenc with node's way.
var time = new Date(message.timestamp * 1000);
var expected = `${time.toLocaleDateString()} 07:12:53 (UTC+0)`;
timerender.set_full_datetime(message, time_element);
var actual = attrs.get('title');
assert.equal(expected, actual);

View File

@@ -1,5 +1,8 @@
set_global('page_params', {realm_is_zephyr_mirror_realm: false});
set_global('templates', {});
set_global('md5', function (s) {
return 'md5-' + s;
});
zrequire('Handlebars', 'handlebars');
zrequire('recent_senders');

View File

@@ -32,12 +32,11 @@ var upload_opts = upload.options({ mode: "compose" });
assert(handler);
};
$("#compose-error-msg").html('');
var test_html = '<div class="progress progress-striped active">' +
'<div class="bar" id="compose-upload-bar" style="width: 00%;">' +
'</div></div>';
$("<p>").after = function (html) {
var test_html = '<div class="progress active">' +
'<div class="bar" id="compose-upload-bar" style="width: 0"></div>' +
'</div>';
$("#compose-send-status").append = function (html) {
assert.equal(html, test_html);
return 'fake-html';
};
upload_opts.drop();
@@ -46,7 +45,6 @@ var upload_opts = upload.options({ mode: "compose" });
assert($("#compose-send-status").hasClass("alert-info"));
assert($("#compose-send-status").visible());
assert.equal($("<p>").text(), 'translated: Uploading…');
assert.equal($("#compose-error-msg").html(), 'fake-html');
}());
(function test_progress_updated() {
@@ -65,6 +63,10 @@ var upload_opts = upload.options({ mode: "compose" });
$("#compose-send-status").addClass("alert-info");
$("#compose-send-button").attr("disabled", 'disabled');
$("#compose-error-msg").text('');
$("#compose-upload-bar").parent = function () {
return { remove: function () {} };
};
}
function assert_side_effects(msg) {
@@ -144,8 +146,17 @@ var upload_opts = upload.options({ mode: "compose" });
}
}
global.patch_builtin('setTimeout', function (func) {
func();
});
$("#compose-upload-bar").width = function (width_percent) {
assert.equal(width_percent, '100%');
};
setup();
upload_opts.uploadFinished(i, {}, response);
upload_opts.progressUpdated(1, '', 100);
assert_side_effects();
}

View File

@@ -166,6 +166,13 @@ zrequire('util');
'some text before only @**everyone**',
];
var messages_with_stream_mentions = [
'@**stream**',
'some text before @**stream** some text after',
'@**stream** some text after only',
'some text before only @**stream**',
];
var messages_without_all_mentions = [
'@all',
'some text before @all some text after',
@@ -183,6 +190,16 @@ zrequire('util');
'`@**everyone**`',
'some_email@**everyone**.com',
];
var messages_without_stream_mentions = [
'some text before @stream some text after',
'@stream',
'`@stream`',
'some_email@stream.com',
'`@**stream**`',
'some_email@**stream**.com',
];
var i;
for (i=0; i<messages_with_all_mentions.length; i += 1) {
assert(util.is_all_or_everyone_mentioned(messages_with_all_mentions[i]));
@@ -192,6 +209,10 @@ zrequire('util');
assert(util.is_all_or_everyone_mentioned(messages_with_everyone_mentions[i]));
}
for (i=0; i<messages_with_stream_mentions.length; i += 1) {
assert(util.is_all_or_everyone_mentioned(messages_with_stream_mentions[i]));
}
for (i=0; i<messages_without_all_mentions.length; i += 1) {
assert(!util.is_all_or_everyone_mentioned(messages_without_everyone_mentions[i]));
}
@@ -199,6 +220,10 @@ zrequire('util');
for (i=0; i<messages_without_everyone_mentions.length; i += 1) {
assert(!util.is_all_or_everyone_mentioned(messages_without_everyone_mentions[i]));
}
for (i=0; i<messages_without_stream_mentions.length; i += 1) {
assert(!util.is_all_or_everyone_mentioned(messages_without_stream_mentions[i]));
}
}());
(function test_move_array_elements_to_front() {

View File

@@ -174,3 +174,26 @@ set_global('$', global.make_zjquery());
obj2.addClass('.striped');
assert(obj2.hasClass('.striped'));
}());
(function test_extensions() {
// You can extend $.fn so that all subsequent objects
// we create get a new function.
$.fn.area = function () {
return this.width() * this.height();
};
// Before we use area, though, let's illustrate that
// the predominant Zulip testing style is to stub objects
// using direct syntax:
var rect = $.create('rectangle');
rect.width = () => { return 5; };
rect.height = () => { return 7; };
assert.equal(rect.width(), 5);
assert.equal(rect.height(), 7);
// But we also have area available from general extension.
assert.equal(rect.area(), 35);
}());

View File

@@ -2,10 +2,19 @@ var noop = function () {};
var exports = {};
exports.make_zjquery = function () {
exports.make_zjquery = function (opts) {
var elems = {};
// Our fn structure helps us simulate extending jQuery.
var fn = {};
function add_extensions(obj) {
_.each(fn, (v, k) => {
obj[k] = v;
});
}
function new_elem(selector) {
var html = 'never-been-set';
var text = 'never-been-set';
@@ -82,7 +91,9 @@ exports.make_zjquery = function () {
if (child) {
return child;
}
if (opts.silent) {
return self;
}
throw Error("Cannot find " + child_selector + " in " + selector);
},
focus: function () {
@@ -219,6 +230,9 @@ exports.make_zjquery = function () {
return self;
},
removeData: noop,
replaceWith: function () {
return self;
},
select: function (arg) {
generic_event('select', arg);
return self;
@@ -253,7 +267,7 @@ exports.make_zjquery = function () {
// legitimate for code to trigger multiple handlers.
// But up until now, we haven't needed this, and if
// you come across this assertion, it's possible that
// you can sselfify your tests by just doing your own
// you can simplify your tests by just doing your own
// mocking of trigger(). If you really know what you
// are doing, you can remove this limitation.
assert(funcs.length <= 1, 'multiple functions set up');
@@ -281,10 +295,13 @@ exports.make_zjquery = function () {
self[0] = 'you-must-set-the-child-yourself';
add_extensions(self);
self.selector = selector;
return self;
}
var zjquery = function (arg) {
var zjquery = function (arg, arg2) {
if (typeof arg === "function") {
// If somebody is passing us a function, we emulate
// jQuery's behavior of running this function after
@@ -292,25 +309,44 @@ exports.make_zjquery = function () {
// so we just call it right away.
arg();
return;
} else if (typeof arg === "object") {
// If somebody is passing us an element, we return
// the element itself if it's been created with
// zjquery.
// This may happen in cases like $(this).
if (arg.debug) {
var this_selector = arg.debug().selector;
if (elems[this_selector]) {
return arg;
}
}
// If somebody is passing us an element, we return
// the element itself if it's been created with
// zjquery.
// This may happen in cases like $(this).
if (arg.selector) {
if (elems[arg.selector]) {
return arg;
}
}
// We occasionally create stub objects that know
// they want to be wrapped by jQuery (so they can
// in turn return stubs). The convention is that
// they provide a to_$ attribute.
if (arg.to_$) {
assert(typeof arg.to_$ === "function");
return arg.to_$();
}
if (arg2 !== undefined) {
throw Error("We only use one-argument variations of $(...) in Zulip code.");
}
var selector = arg;
if (typeof selector !== "string") {
console.info(arg);
throw Error("zjquery does not know how to wrap this object yet");
}
var valid_selector =
('<#.'.indexOf(selector[0]) >= 0) ||
(selector === 'window-stub') ||
(selector === 'document-stub') ||
(selector === 'body') ||
(selector === 'html') ||
(selector.location) ||
(selector.indexOf('#') >= 0) ||
@@ -369,6 +405,8 @@ exports.make_zjquery = function () {
return _.extend(content, container);
};
zjquery.fn = fn;
return zjquery;
};

View File

@@ -8,7 +8,7 @@
"@types/node": "8.0.34",
"@types/webpack": "3.0.13",
"blueimp-md5": "2.10.0",
"clipboard": "1.5.16",
"clipboard": "2.0.0",
"emoji-datasource-apple": "3.0.0",
"emoji-datasource-emojione": "3.0.0",
"emoji-datasource-google": "3.0.0",
@@ -32,6 +32,8 @@
"script-loader": "0.7.2",
"source-map-loader": "0.2.3",
"string.prototype.codepointat": "0.2.0",
"string.prototype.endswith": "0.2.0",
"string.prototype.startswith": "0.2.0",
"to-markdown": "3.1.0",
"ts-loader": "2.1.0",
"ts-node": "3.3.0",
@@ -48,7 +50,7 @@
"casperjs": "casperjs/casperjs",
"cssstyle": "0.2.29",
"difflib": "0.2.4",
"eslint": "3.9.1",
"eslint": "4.19.1",
"eslint-plugin-empty-returns": "1.0.2",
"htmlparser2": "3.8.3",
"istanbul": "0.4.5",

View File

@@ -14,6 +14,7 @@ server {
location /user_avatars {
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'none' img-src 'self'";
include /etc/nginx/zulip-include/uploads.types;
alias /home/zulip/uploads/avatars;
}

View File

@@ -2,6 +2,8 @@
types {
text/plain txt;
application/pdf pdf;
image/gif gif;
image/jpeg jpeg jpg;
image/png png;

View File

@@ -1,5 +1,6 @@
location /user_uploads {
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;";
include /etc/nginx/zulip-include/uploads.types;
alias /home/zulip/uploads/files;
}

View File

@@ -1,6 +1,7 @@
location /serve_uploads {
internal;
add_header X-Content-Type-Options nosniff;
add_header Content-Security-Policy "default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; object-src 'self'; plugin-types application/pdf;";
include /etc/nginx/zulip-include/uploads.types;
alias /home/zulip/uploads/files;
}

View File

@@ -95,7 +95,8 @@ oauthlib==2.0.7
ndg-httpsclient==0.4.4
# Needed to access rabbitmq
pika==0.11.2
# See #8466 for why we're not using the latest version.
pika==0.11.0
# Needed to access our database
psycopg2==2.7.4 --no-binary psycopg2
@@ -164,11 +165,12 @@ ijson==2.3
beautifulsoup4==4.6.0
pyoembed==0.1.2
# The Zulip API bindings, from its own repository.
# We integrate with these tightly, so often it makes sense to pin a
# version from Git rather than a release.
-e "git+https://github.com/zulip/python-zulip-api.git@0.4.2#egg=zulip==0.4.2_git&subdirectory=zulip"
-e "git+https://github.com/zulip/python-zulip-api.git@0.4.2#egg=zulip_bots==0.4.2+git&subdirectory=zulip_bots"
# The Zulip API bindings, from its own repository. We integrate with
# these tightly, including fetching content not included in the normal
# release tarballs (which is a bug). So we need to pin it makes sense
# to pin a version from Git rather than a release.
-e "git+https://github.com/zulip/python-zulip-api.git@0.4.4#egg=zulip==0.4.4_git&subdirectory=zulip"
-e "git+https://github.com/zulip/python-zulip-api.git@0.4.4#egg=zulip_bots==0.4.4+git&subdirectory=zulip_bots"
# Used for Hesiod lookups, etc.
py3dns==3.1.0

View File

@@ -11,8 +11,8 @@
git+https://github.com/zulip/talon.git@7d8bdc4dbcfcc5a73298747293b99fe53da55315#egg=talon==1.2.10.zulip1
git+https://github.com/zulip/ultrajson@70ac02bec#egg=ujson==1.35+git
git+https://github.com/zulip/python-zulip-api.git@0.4.2#egg=zulip==0.4.2_git&subdirectory=zulip
git+https://github.com/zulip/python-zulip-api.git@0.4.2#egg=zulip_bots==0.4.2+git&subdirectory=zulip_bots
git+https://github.com/zulip/python-zulip-api.git@0.4.4#egg=zulip==0.4.4_git&subdirectory=zulip
git+https://github.com/zulip/python-zulip-api.git@0.4.4#egg=zulip_bots==0.4.4+git&subdirectory=zulip_bots
alabaster==0.7.10
apns2==0.3.0
argon2-cffi==18.1.0
@@ -101,7 +101,7 @@ pbr==3.1.1 # via mock
pexpect==4.3.0 # via ipython
phonenumberslite==8.8.6 # via django-phonenumber-field
pickleshare==0.7.4 # via ipython
pika==0.11.2
pika==0.11.0
pillow==5.0.0
pip-tools==1.11.0
polib==1.1.0

View File

@@ -11,8 +11,8 @@
git+https://github.com/zulip/talon.git@7d8bdc4dbcfcc5a73298747293b99fe53da55315#egg=talon==1.2.10.zulip1
git+https://github.com/zulip/ultrajson@70ac02bec#egg=ujson==1.35+git
git+https://github.com/zulip/python-zulip-api.git@0.4.2#egg=zulip==0.4.2_git&subdirectory=zulip
git+https://github.com/zulip/python-zulip-api.git@0.4.2#egg=zulip_bots==0.4.2+git&subdirectory=zulip_bots
git+https://github.com/zulip/python-zulip-api.git@0.4.4#egg=zulip==0.4.4_git&subdirectory=zulip
git+https://github.com/zulip/python-zulip-api.git@0.4.4#egg=zulip_bots==0.4.4+git&subdirectory=zulip_bots
apns2==0.3.0
argon2-cffi==18.1.0
asn1crypto==0.23.0 # via cryptography
@@ -71,7 +71,7 @@ pbr==3.1.1 # via mock
pexpect==4.3.0 # via ipython
phonenumberslite==8.8.6 # via django-phonenumber-field
pickleshare==0.7.4 # via ipython
pika==0.11.2
pika==0.11.0
pillow==5.0.0
polib==1.1.0
premailer==3.1.1

View File

@@ -7,8 +7,13 @@ if [ "$TRAVIS" ] ; then
ZULIP_SRV="/home/travis"
fi
YARN_BIN="$ZULIP_SRV/zulip-yarn/bin/yarn"
node_version=6.6.0
yarn_version=0.27.5
node_version=8.11.1
yarn_version=1.5.1
# This is a fix for the fact that nvm uses $HOME to determine which
# user account's home directory to ~/.config to. Ideally, we'd have a
# more systematic fix, like using `sudo -H` everywhere.
export HOME=/root
current_node_version="none"
if hash node 2>/dev/null; then
@@ -23,7 +28,7 @@ fi
if [ "$current_node_version" != "v$node_version" ]; then
export NVM_DIR=/usr/local/nvm
if ! [ -e "$NVM_DIR/nvm.sh" ]; then
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh | bash
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
fi
source "$NVM_DIR/nvm.sh"
@@ -41,5 +46,11 @@ if [ "$current_node_version" != "v$node_version" ]; then
sed -i "s|NODE_PATH|$NODE_BIN|" /usr/local/bin/node
fi
# Work around the fact that apparently sudo doesn't clear the HOME
# environment variable in some cases; we don't want root
# accessing/storing yarn configuration in the non-root user's home
# directory.
export HOME=/root
# Install yarn if not installed
bash "$ZULIP_PATH/scripts/lib/third/install-yarn.sh" "$ZULIP_SRV" --version "$yarn_version"

View File

@@ -68,7 +68,8 @@ yarn_verify_integrity() {
printf "Verifying integrity...\n"
# Grab the public key if it doesn't already exist
gpg --list-keys $gpg_key >/dev/null 2>&1 || (curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --import)
# Zulip patch: Fix the fact that Yarn has extended this keyring and we should always redownload.
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --import
if [ ! -f "$1.asc" ]; then
printf "$red> Could not download GPG signature for this Yarn release. This means the release cannot be verified!$reset\n"

View File

@@ -1,5 +0,0 @@
#!/bin/bash
set -e
./manage.py sqlsequencereset zerver | ./manage.py dbshell
echo "Sequence has been reset successfully!"

View File

@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="96px" height="96px" viewBox="21 0 75 75" enable-background="new 0 0 96 96" xml:space="preserve">
<g>
<path fill="#00cbc3" d="M49.844,68.325c-1.416,0-2.748-0.554-3.75-1.557L27.523,48.191c-1.003-1.002-1.555-2.334-1.555-3.75 s0.552-2.749,1.555-3.75c1.001-1.001,2.333-1.552,3.75-1.552s2.75,0.551,3.753,1.553l14.019,14.017L82.14,5.504 c0.989-1.468,2.639-2.345,4.412-2.345c1.054,0,2.075,0.312,2.956,0.902c2.424,1.631,3.07,4.934,1.439,7.361L54.25,65.98 c-0.892,1.316-2.312,2.162-3.895,2.314C50.17,68.315,50.01,68.325,49.844,68.325z"/>
<path fill="#5bbd95" d="M49.844,68.325c-1.416,0-2.748-0.554-3.75-1.557L27.523,48.191c-1.003-1.002-1.555-2.334-1.555-3.75 s0.552-2.749,1.555-3.75c1.001-1.001,2.333-1.552,3.75-1.552s2.75,0.551,3.753,1.553l14.019,14.017L82.14,5.504 c0.989-1.468,2.639-2.345,4.412-2.345c1.054,0,2.075,0.312,2.956,0.902c2.424,1.631,3.07,4.934,1.439,7.361L54.25,65.98 c-0.892,1.316-2.312,2.162-3.895,2.314C50.17,68.315,50.01,68.325,49.844,68.325z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="266.893" height="266.895"><path fill="#3C5A99" d="M248.082 262.307c7.854 0 14.223-6.369 14.223-14.225V18.812c0-7.857-6.368-14.224-14.223-14.224H18.812c-7.857 0-14.224 6.367-14.224 14.224v229.27c0 7.855 6.366 14.225 14.224 14.225h229.27z"/><path fill="#FFF" d="M182.409 262.307v-99.803h33.499l5.016-38.895h-38.515V98.777c0-11.261 3.127-18.935 19.275-18.935l20.596-.009V45.045c-3.562-.474-15.788-1.533-30.012-1.533-29.695 0-50.025 18.126-50.025 51.413v28.684h-33.585v38.895h33.585v99.803h40.166z"/></svg>

Before

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -82,8 +82,8 @@ function _setup_page() {
};
options.bot_creation_policy_values = settings_bots.bot_creation_policy_values;
var admin_tab = templates.render('admin_tab', options);
$("#settings_content .organization-box").html(admin_tab);
var rendered_admin_tab = templates.render('admin_tab', options);
$("#settings_content .organization-box").html(rendered_admin_tab);
$("#settings_content .alert").removeClass("show");
settings_bots.update_bot_settings_tip();

View File

@@ -52,6 +52,7 @@ exports.toggle = (function () {
elem.addClass("selected");
if (idx !== meta.idx) {
meta.idx = idx;
if (opts.callback) {
opts.callback(
opts.values[idx].label,
@@ -61,19 +62,22 @@ exports.toggle = (function () {
}
}
meta.idx = idx;
elem.focus();
if (!opts.child_wants_focus) {
elem.focus();
}
}
function maybe_go_left() {
if (meta.idx > 0) {
select_tab(meta.idx - 1);
return true;
}
}
function maybe_go_right() {
if (meta.idx < opts.values.length - 1) {
select_tab(meta.idx + 1);
return true;
}
}
@@ -83,22 +87,24 @@ exports.toggle = (function () {
select_tab(idx);
});
meta.$ind_tab.keydown(function (e) {
var key = e.which || e.keyCode;
if (key === 37) {
maybe_go_left();
} else if (key === 39) {
maybe_go_right();
}
keydown_util.handle({
elem: meta.$ind_tab,
handlers: {
left_arrow: maybe_go_left,
right_arrow: maybe_go_right,
},
});
// We should arguably default opts.selected to 0.
if (typeof opts.selected === "number") {
select_tab(opts.selected);
}
}());
var prototype = {
maybe_go_left: maybe_go_left,
maybe_go_right: maybe_go_right,
value: function () {
if (meta.idx >= 0) {
return opts.values[meta.idx].label;

View File

@@ -185,6 +185,19 @@ function nonexistent_stream_reply_error() {
}, 5000);
}
function compose_not_subscribed_error(error_text, bad_input) {
$('#compose-send-status').removeClass(common.status_classes)
.addClass('home-error-bar')
.stop(true).fadeTo(0, 1);
$('#compose-error-msg').html(error_text);
$("#compose-send-button").prop('disabled', false);
$("#sending-indicator").hide();
$(".compose-send-status-close").hide();
if (bad_input !== undefined) {
bad_input.focus().select();
}
}
exports.nonexistent_stream_reply_error = nonexistent_stream_reply_error;
function clear_compose_box() {
@@ -513,8 +526,8 @@ exports.validation_error = function (error_type, stream_name) {
compose_error(i18n.t("Error checking subscription"), $("#stream"));
return false;
case "not-subscribed":
response = i18n.t("<p>You're not subscribed to the stream <b>__stream_name__</b>.</p><p>Manage your subscriptions <a href='#streams/all'>on your Streams page</a>.</p>", context);
compose_error(response, $('#stream'));
var new_row = templates.render("compose_not_subscribed");
compose_not_subscribed_error(new_row, $('#stream'));
return false;
}
return true;
@@ -692,22 +705,6 @@ exports.initialize = function () {
upload.feature_check($("#compose #attach_files"));
// Lazy load the Dropbox script, since it can slow our page load
// otherwise, and isn't enabled for all users. Also, this Dropbox
// script isn't under an open source license, so we can't (for legal
// reasons) minify it with our own code.
if (feature_flags.dropbox_integration) {
LazyLoad.js('https://www.dropbox.com/static/api/1/dropins.js', function () {
// Successful load. We should now have window.Dropbox.
if (! _.has(window, 'Dropbox')) {
blueslip.error('Dropbox script reports loading but window.Dropbox undefined');
} else if (Dropbox.isBrowserSupported()) {
Dropbox.init({appKey: window.dropboxAppKey});
$("#compose #attach_dropbox_files").removeClass("notdisplayed");
}
});
}
// Show a warning if a user @-mentions someone who will not receive this message
$(document).on('usermention_completed.zulip', function (event, data) {
if (compose_state.get_message_type() !== 'stream') {
@@ -722,8 +719,8 @@ exports.initialize = function () {
if (data !== undefined && data.mentioned !== undefined) {
var email = data.mentioned.email;
// warn if @all or @everyone is mentioned
if (data.mentioned.full_name === 'all' || data.mentioned.full_name === 'everyone') {
// warn if @all, @everyone or @stream is mentioned
if (data.mentioned.full_name === 'all' || data.mentioned.full_name === 'everyone' || data.mentioned.full_name === 'stream') {
return; // don't check if @all or @everyone is subscribed to a stream
}
@@ -769,6 +766,24 @@ exports.initialize = function () {
compose.finish();
});
$("#compose-send-status").on('click', '.sub_unsub_button', function (event) {
event.preventDefault();
var stream_name = $('#stream').val();
if (stream_name === undefined) {
return;
}
var sub = stream_data.get_sub(stream_name);
subs.sub_or_unsub(sub);
$("#compose-send-status").hide();
});
$("#compose-send-status").on('click', '#compose_not_subscribed_close', function (event) {
event.preventDefault();
$("#compose-send-status").hide();
});
$("#compose_invite_users").on('click', '.compose_invite_link', function (event) {
event.preventDefault();
@@ -885,15 +900,15 @@ exports.initialize = function () {
// content is passed to check for status messages ("/me ...")
// and will be undefined in case of errors
function show_preview(rendered_content, content) {
var preview_html;
var rendered_preview_html;
if (content !== undefined && markdown.is_status_message(content, rendered_content)) {
// Handle previews of /me messages
preview_html = "<strong>" + page_params.full_name + "</strong> " + rendered_content.slice(4 + 3, -4);
rendered_preview_html = "<strong>" + page_params.full_name + "</strong> " + rendered_content.slice(4 + 3, -4);
} else {
preview_html = rendered_content;
rendered_preview_html = rendered_content;
}
$("#preview_content").html(preview_html);
$("#preview_content").html(rendered_preview_html);
if (page_params.emojiset === "text") {
$("#preview_content").find(".emoji").replaceWith(function () {
var text = $(this).attr("title");
@@ -967,24 +982,6 @@ exports.initialize = function () {
exports.clear_preview_area();
});
$("#compose").on("click", "#attach_dropbox_files", function (e) {
e.preventDefault();
var options = {
// Required. Called when a user selects an item in the Chooser.
success: function (files) {
var textbox = $("#compose-textarea");
var links = _.map(files, function (file) { return '[' + file.name + '](' + file.link +')'; })
.join(' ') + ' ';
textbox.val(textbox.val() + links);
},
// Optional. A value of false (default) limits selection to a single file, while
// true enables multiple file selection.
multiselect: true,
iframe: true,
};
Dropbox.choose(options);
});
$("#compose").filedrop(
upload.options({
mode: 'compose',

View File

@@ -288,7 +288,7 @@ exports.respond_to_message = function (opts) {
return;
}
unread_ops.mark_message_as_read(message);
unread_ops.notify_server_message_read(message);
var stream = '';
var subject = '';
@@ -349,28 +349,26 @@ exports.on_topic_narrow = function () {
return;
}
if (compose_state.subject()) {
// If the user has filled in a subject, we have
// a risk of a mix, and we can't reliably guess
// whether the old topic is appropriate (otherwise,
// why did they narrow?) or the new one is
// appropriate (after all, they were starting to
// compose on the old topic and may now be looking
// for info), so we punt and cancel.
// If subject is not same as topic narrowed to then
// stop composing
if (compose_state.subject().toLowerCase() !== narrow_state.topic().toLowerCase()) {
exports.cancel();
}
if (compose_state.subject() && compose_state.has_message_content()) {
// If the user has written something to a different topic,
// they probably want that content, so leave compose open.
//
// This effectively uses the heuristic of whether there is
// content in compose to determine whether the user had firmly
// decided to compose to the old topic or is just looking to
// reply to what they see.
compose_fade.update_message_list();
return;
}
// If we got this far, then the compose box has the correct
// stream filled in, and we just need to update the topic.
// See #3300 for context--a couple users specifically asked
// for this convenience.
// If we got this far, then the compose box has the correct stream
// filled in, and either compose is empty or no topic was set, so
// we should update the compose topic to match the new narrow.
// See #3300 for context--a couple users specifically asked for
// this convenience.
compose_state.subject(narrow_state.topic());
compose_fade.set_focused_recipient("stream");
compose_fade.update_message_list();
$('#compose-textarea').focus().select();
};

View File

@@ -118,8 +118,8 @@ exports.would_receive_message = function (email) {
if (focused_recipient.type === 'stream') {
var user = people.get_active_user_for_email(email);
var sub = stream_data.get_sub(focused_recipient.stream);
if (!sub) {
// If the stream isn't valid, there is no risk of a mix
if (!sub || !user) {
// If the stream or user isn't valid, there is no risk of a mix
// yet, so don't fade.
return;
}
@@ -129,7 +129,7 @@ exports.would_receive_message = function (email) {
// not subscribed.
return;
}
return stream_data.user_is_subscribed(focused_recipient.stream, email);
return stream_data.is_user_subscribed(focused_recipient.stream, user.user_id);
}
// PM, so check if the given email is in the recipients list.

View File

@@ -374,23 +374,20 @@ exports.compose_content_begins_typeahead = function (query) {
this.completing = 'mention';
this.token = current_token;
var all_item = {
special_item_text: "all (Notify everyone)",
email: "all",
// Always sort above, under the assumption that names will
// be longer and only contain "all" as a substring.
pm_recipient_count: Infinity,
full_name: "all",
};
var everyone_item = {
special_item_text: "everyone (Notify everyone)",
email: "everyone",
pm_recipient_count: Infinity,
full_name: "everyone",
};
var all_items = _.map(['all', 'everyone', 'stream'], function (mention) {
return {
special_item_text: i18n.t("__wildcard_mention_token__ (Notify stream)",
{wildcard_mention_token: mention}),
email: mention,
// Always sort above, under the assumption that names will
// be longer and only contain "all" as a substring.
pm_recipient_count: Infinity,
full_name: mention,
};
});
var persons = people.get_realm_persons();
var groups = user_groups.get_realm_user_groups();
return [].concat(persons, [all_item, everyone_item], groups);
return [].concat(persons, all_items, groups);
}
if (this.options.completions.stream && current_token[0] === '#') {

View File

@@ -224,7 +224,7 @@ exports.paste_handler = function (event) {
if (clipboardData.getData) {
var paste_html = clipboardData.getData('text/html');
if (paste_html) {
if (paste_html && page_params.development_environment) {
event.preventDefault();
var text = exports.paste_handler_converter(paste_html);
compose_ui.insert_syntax_and_focus(text);

View File

@@ -247,11 +247,11 @@ function filter_emojis() {
});
});
});
var search_results_rendered = templates.render('emoji_popover_search_results', {
var rendered_search_results = templates.render('emoji_popover_search_results', {
search_results: search_results,
message_id: message_id,
});
$('.emoji-search-results').html(search_results_rendered);
$('.emoji-search-results').html(rendered_search_results);
ui.update_scrollbar($(".emoji-search-results-container"));
if (!search_results_visible) {
show_search_results();

View File

@@ -13,7 +13,6 @@ exports.mark_read_at_bottom = true;
exports.propagate_topic_edits = true;
exports.clicking_notification_causes_narrow = true;
exports.collapsible = false;
exports.dropbox_integration = false;
exports.reminders_in_message_action_menu = false;
return exports;

View File

@@ -483,7 +483,7 @@ Filter.operator_to_prefix = function (operator, negated) {
};
// Convert a list of operators to a human-readable description.
Filter.describe = function (operators) {
function describe_unescaped(operators) {
if (operators.length === 0) {
return 'all messages';
}
@@ -530,8 +530,11 @@ Filter.describe = function (operators) {
return "unknown operator";
});
return parts.concat(more_parts).join(', ');
};
}
Filter.describe = function (operators) {
return Handlebars.Utils.escapeExpression(describe_unescaped(operators));
};
return Filter;

View File

@@ -21,10 +21,15 @@ function adjust_mac_shortcuts() {
});
}
// Make it explicit that our toggler is undefined until
// _setup_info_overlay is called via ensure_i18n.
exports.toggler = undefined;
function _setup_info_overlay() {
var info_overlay_toggle = components.toggle({
var opts = {
name: "info-overlay-toggle",
selected: 0,
child_wants_focus: true,
values: [
{ label: i18n.t("Keyboard shortcuts"), key: "keyboard-shortcuts" },
{ label: i18n.t("Message formatting"), key: "markdown-help" },
@@ -35,14 +40,35 @@ function _setup_info_overlay() {
$("#" + key).show();
$("#" + key).find(".modal-body").focus();
},
}).get();
};
$(".informational-overlays .overlay-tabs")
.append($(info_overlay_toggle).addClass("large"));
var toggler = components.toggle(opts);
var elem = toggler.get();
elem.addClass('large');
var modals = _.map(opts.values, function (item) {
var key = item.key; // e.g. markdown-help
var modal = $('#' + key).find('.modal-body');
return modal;
});
_.each(modals, function (modal) {
keydown_util.handle({
elem: modal,
handlers: {
left_arrow: toggler.maybe_go_left,
right_arrow: toggler.maybe_go_right,
},
});
});
$(".informational-overlays .overlay-tabs").append(elem);
if (/Mac/i.test(navigator.userAgent)) {
adjust_mac_shortcuts();
}
exports.toggler = toggler;
}
exports.show = function (target) {
@@ -59,7 +85,9 @@ exports.show = function (target) {
}
if (target) {
components.toggle.lookup("info-overlay-toggle").goto(target);
if (exports.toggler) {
exports.toggler.goto(target);
}
}
};

View File

@@ -367,6 +367,61 @@ exports.create = function (opts) {
return prototype;
};
// Following function is used for creating non-editable pills.
exports.create_non_editable_pills = function (opts) {
if (!opts.container) {
blueslip.error('Pill needs container.');
return;
}
var store = {
pills: [],
$parent: opts.container,
};
var funcs = {
// This is generally called by typeahead logic, where we have all
// the data we need (as opposed to, say, just a user-typed email).
appendValidatedData: function (item) {
var id = exports.random_id();
if (!item.display_value) {
blueslip.error('no display_value returned');
return;
}
var payload = {
id: id,
item: item,
};
store.pills.push(payload);
var opts = {
id: payload.id,
display_value: item.display_value,
cannot_edit: true,
};
var pill_html = templates.render('input_pill', opts);
payload.$element = $(pill_html);
store.$parent.append(payload.$element);
},
items: function () {
return _.pluck(store.pills, 'item');
},
};
var prototype = {
appendValidatedData: funcs.appendValidatedData.bind(funcs),
items: funcs.items,
};
return prototype;
};
return exports;
}());

View File

@@ -76,8 +76,8 @@ exports.initialize = function () {
invitee_emails.val('');
if (page_params.development_environment) {
var email_msg = templates.render('dev_env_email_access');
$('#dev_env_msg').html(email_msg).addClass('alert-info').show();
var rendered_email_msg = templates.render('dev_env_email_access');
$('#dev_env_msg').html(rendered_email_msg).addClass('alert-info').show();
}
},

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