Move planner under canvas-lms

closes: CORE-949

this also upgrades us to use what was on canvas-planner master
as of 1pm on 2/1/2018 (specifically 9569cc1)

test plan:
* canvas planner should work
* all automated builds should pass

Change-Id: Iecce81d640c8aacb79189e2b26946613a03d25f2
Reviewed-on: https://gerrit.instructure.com/135947
Reviewed-by: Clay Diffrient <cdiffrient@instructure.com>
Tested-by: Jenkins
Product-Review: Ryan Shaw <ryan@instructure.com>
QA-Review: Ryan Shaw <ryan@instructure.com>
This commit is contained in:
Ed Schiebel 2017-12-18 09:39:39 -05:00 committed by Ryan Shaw
parent fcff3ab3e1
commit 40b876493e
221 changed files with 35953 additions and 28 deletions

7
.gitignore vendored
View File

@ -59,3 +59,10 @@ yarn-error.log
# user docker compose overrides
docker-compose.local.*
# sub-packages
/packages/*/node_modules
/packages/canvas-planner/lib
/packages/canvas-planner/es
/packages/canvas-planner/coverage/
/packages/canvas-planner/.babel-cache

View File

@ -58,10 +58,11 @@ COPY Gemfile ${APP_HOME}
COPY Gemfile.d ${APP_HOME}Gemfile.d
COPY config ${APP_HOME}config
COPY gems ${APP_HOME}gems
COPY packages ${APP_HOME}packages
COPY script ${APP_HOME}script
COPY package.json ${APP_HOME}
COPY yarn.lock ${APP_HOME}
RUN find gems -type d ! -user docker -print0 | xargs -0 chown -h docker:docker
RUN find gems packages -type d ! -user docker -print0 | xargs -0 chown -h docker:docker
# Install deps as docker to avoid sadness w/ npm lifecycle hooks
USER docker

View File

@ -109,10 +109,11 @@ COPY Gemfile ${APP_HOME}
COPY Gemfile.d ${APP_HOME}Gemfile.d
COPY config ${APP_HOME}config
COPY gems ${APP_HOME}gems
COPY packages ${APP_HOME}packages
COPY script ${APP_HOME}script
COPY package.json ${APP_HOME}
COPY yarn.lock ${APP_HOME}
RUN find gems -type d ! -user docker -print0 | xargs -0 chown -h docker:docker
RUN find gems packages -type d ! -user docker -print0 | xargs -0 chown -h docker:docker
# Install deps as docker to avoid sadness w/ npm lifecycle hooks
USER docker

View File

@ -13,7 +13,7 @@
"axios": "^0.16.0",
"backbone": "1.1.1",
"brandable_css": "0.1.0",
"canvas-planner": "1.0.16",
"canvas-planner": "file:./packages/canvas-planner",
"canvas-rce": "2.6.3",
"canvas_offline_course_viewer": "https://github.com/instructure/canvas_offline_course_viewer.git#1.2.0",
"classnames": "^2.2.5",
@ -153,7 +153,7 @@
},
"repository": "instructure/canvas-lms",
"scripts": {
"test": "concurrently --names \"jest,karma\" \"jest\" \"if [ \"$COVERAGE\" ]; then yarn test:karma; else yarn test:karma:concurrently; fi\"",
"test": "concurrently --names \"packages,jest,karma\" \"yarn test:packages\" \"jest\" \"if [ \"$COVERAGE\" ]; then yarn test:karma; else yarn test:karma:concurrently; fi\"",
"test:watch": "concurrently --names \"jest,karma\" \"jest --watch .\" \"yarn test:karma:watch\"",
"test:jest": "echo 'this is here just so you know you can use jest, but just use the `jest` cli directly' && jest",
"test:karma": "karma start --single-run",
@ -161,14 +161,15 @@
"test:karma:headless": "karma start --single-run --browsers ChromeHeadlessNoSandbox",
"test:karma:watch": "karma start",
"test:karma:watch:headless": "karma start --browsers ChromeHeadlessNoSandbox",
"preinstall": "script/gem_npm install",
"preupdate": "script/gem_npm update",
"test:packages": "(cd packages/canvas-planner; yarn test)",
"preinstall": "script/local-yarn install",
"build": "yarn run build:css && yarn run build:js",
"build:watch": "concurrently \"yarn build:css:watch\" \"yarn build:js:watch\"",
"build:css": "brandable_css",
"build:css:watch": "brandable_css --watch",
"build:js": "yarn run webpack-development",
"build:js:watch": "yarn run webpack",
"build:packages": "script/yarn-packages build",
"webpack": "gulp rev & webpack --progress --color --watch",
"webpack-development": "gulp rev & webpack --progress --color",
"webpack-production": "NODE_ENV=production gulp rev & node --max_old_space_size=4096 $(yarn bin)/webpack --color --config webpack.production.config.js",

View File

@ -0,0 +1,12 @@
{
"presets": [["./babel-preset"]],
"env": {
"test": {
"presets": [["./babel-preset"]]
}
},
"plugins": [
"inline-react-svg",
"transform-regenerator",
]
}

View File

@ -0,0 +1,2 @@
public/ie11-polyfill.js
copyright-template.js

View File

@ -0,0 +1,17 @@
{
"extends": ["react-app", "plugin:react/recommended"],
"plugins": [
"react",
"notice"
],
"rules": {
"no-extra-semi": "error",
"no-trailing-spaces": "error",
"semi": "error",
"semi-spacing": "error",
"notice/notice": ["error", {
"mustMatch": "Copyright \\(C\\) [0-9]{0,4} - present Instructure, Inc.",
"templateFile": "copyright-template.js"
}]
}
}

View File

@ -0,0 +1,106 @@
{
"rules": {
"at-rule-empty-line-before": [
"always", {
"except": ["first-nested", "blockless-after-blockless"]
} ],
"at-rule-no-vendor-prefix": true,
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-opening-brace-newline-after": "always",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-no-invalid-hex": true,
"comment-empty-line-before": [ "always", {
"except": ["first-nested"],
"ignore": ["stylelint-commands", "after-comment"]
} ],
"comment-whitespace-inside": "always",
"declaration-no-important": true,
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-space-before": "never",
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"declaration-block-no-duplicate-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "always-multi-line",
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-parentheses-newline-inside": "always-multi-line",
"function-parentheses-space-inside": "never-single-line",
"function-url-quotes": "always",
"function-whitespace-after": "always",
"indentation": 2,
"length-zero-no-unit": true,
"max-line-length": [ 120, {
"ignore": "non-comments",
} ],
"max-nesting-depth": 3,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-no-vendor-prefix": true,
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"media-query-list-comma-newline-after": "always-multi-line",
"media-query-list-comma-space-after": "always-single-line",
"media-query-list-comma-space-before": "never",
"media-feature-parentheses-space-inside": "never",
"no-eol-whitespace": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"no-unknown-animations": true,
"number-leading-zero": "always",
"number-max-precision": 4,
"property-no-vendor-prefix": true,
"rule-empty-line-before": [ "always", {
"except": ["first-nested"]
} ],
"selector-class-pattern": "^[a-z]+[a-zA-Z0-9\\-]*[a-zA-Z0-9]*$",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-list-comma-newline-after": "always",
"selector-list-comma-space-before": "never",
"selector-no-id": true,
"selector-no-type": true,
"selector-no-universal": true,
"selector-no-vendor-prefix": true,
"selector-pseudo-element-colon-notation": "double",
"string-no-newline": true,
"string-quotes": "double",
"time-min-milliseconds": 100,
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"value-no-vendor-prefix": true
}
}

View File

@ -0,0 +1,30 @@
FROM instructure/node:8
# the instructure/node:8 docker image uses yarn 0.27 still, we want to use at least 1.3.2
USER root
ENV YARN_VERSION 1.3.2-1
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends yarn="$YARN_VERSION" \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/
USER docker
ENV APP_HOME /usr/src/app
USER root
RUN mkdir -p $APP_HOME
COPY package.json $APP_HOME/
COPY yarn.lock $APP_HOME/
WORKDIR $APP_HOME
RUN yarn
COPY . $APP_HOME
# This makes the container stay running, until explicitly stopped
# rather than being a build only image.
CMD ["tail", "-f", "/dev/null"]

View File

@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,102 @@
canvas-planner
==================
Canvas Planner is the UI component for the List View Dashboard feature in [Canvas](https://github.com/instructure/canvas-lms).
## Production
### Usage
```bash
yarn add canvas-planner
```
```js
import Planner from 'canvas-planner';
```
### Polyfill
Canvas Planner is developed using modern JavaScript and supports modern browsers.
If you are using it in an environment such as IE 11 where some core browser features
are unavailable, then you should make sure to polyfill appropriately. This package
does not ship any polyfills to maintain a smaller footprint.
## Development
### Getting Started
```bash
yarn
yarn start
```
Go to your browser to http://localhost:3005 to see the app. This will
also start a json-server instance at http://localhost:3004 which api requests
will be proxied from webpack-dev-server to eliminating the need to have an
instance of Canvas running to do development.
#### Running without a delay
By default, all requests to the json-server have a 1.5 second delay introduced
to help us develop for proper loading states. If you want to run without the
delay you'll need to instead run:
```bash
yarn run start:json-server:no-delay
```
And then in a separate terminal tab/session/window/etc.
```bash
yarn run start:webpack-dev
```
### Linting
This project uses [eslint-config-react-app](https://github.com/facebookincubator/create-react-app/tree/master/packages/eslint-config-react-app)
for linting JS files. Linting is enforced at the build level. ESLint errors will cause the build to fail.
You can run the linter by running `yarn run lint`
### Testing
We use [Jest](http://facebook.github.io/jest/) for testing the codebase. You can run it
by running `yarn test` for a single run or `yarn test:watch` to start up a watcher process for it.
If you are having trouble with the watch process you may need to set up [watchman] (https://facebook.github.io/watchman/).
It should be as simple as `brew install watchman` on a Mac, no configuration is required. For more details about these
issues see the discussion on the issue, [watch mode stopped working on macOS Sierra](https://github.com/facebook/jest/issues/1767).
We require test coverage percentages to be maintained. Run the test coverage by running `yarn run test:coverage`
### Testing a local Canvas Planner version
If you want to test a version of the planner locally without publishing it you can
do so by using [yarn link](https://yarnpkg.com/en/docs/cli/link).
The way it is done is as follows:
```bash
cd canvas-planner
yarn run build # Build the proper transpiled versions of the files
yarn link
cd ./canvas-lms
yarn link canvas-planner
```
Once you've done those things, run the proper build steps for your Canvas
installation and you'll see your local copy of canvas-planner working inside
Canvas.
### Deploying
To deploy a new version of canvas-planner to npm first update the version field in the package.json.
You will then commit that version to canvas-planner and in your commit message paste the output
of the command below.
`git log v(enter previous version here)...origin/master --pretty=format:"[%h] (%an) %s"`
Next run `./scripts/release` if you have already updated the planner version in your package.json
you can press enter otherwise follow the instructions on screen.
After published go to your canvas-lms directory and open the package.json. Update the canvas-planner
dependency to the one you just released. After that you will need to remove your node_modules and reinstall
using `yarn`. From there you should commit the yarn.lock and the diff in the package.json.

View File

@ -0,0 +1,28 @@
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
if (env === 'test') {
module.exports = {
presets: [
['env', {
modules: 'commonjs',
// use the same polyfills we use in the code we send to browsers
targets: { browsers: require('@instructure/ui-presets/browserslist') }
}],
'stage-1',
'react'
],
plugins: [
'transform-class-display-name',
'transform-node-env-inline'
]
};
} else {
module.exports = {
// eslint-disable-next-line import/no-extraneous-dependencies
presets: [[ require('@instructure/ui-presets/babel'), {
themeable: process.env.BABEL_ENV === 'production',
coverage: false,
esModules: Boolean(process.env.ES_MODULES)
}]]
};
}

View File

@ -0,0 +1,48 @@
#!/bin/bash
function generate_coverage_message() {
statements=$(cat 'coverage/index.html' | grep -B 1 'Statements' | head -1 | egrep -o '1?[0-9]+\.?[0-9]+\%')
branches=$(cat 'coverage/index.html' | grep -B 1 'Branches' | head -1 | egrep -o '1?[0-9]+\.?[0-9]+\%')
functions=$(cat 'coverage/index.html' | grep -B 1 'Functions' | head -1 | egrep -o '1?[0-9]+\.?[0-9]+\%')
lines=$(cat 'coverage/index.html' | grep -B 1 'Lines' | head -1 | egrep -o '1?[0-9]+\.?[0-9]+\%')
echo -e "\nCode Coverage:\n\nStatements: ${statements} | Branches: ${branches} | Functions: ${functions} | Lines: ${lines}\n\nhttps://code-coverage.inseng.net/canvas-planner/coverage/index.html"
}
function cleanup() {
exit_code=$?
: "Cleaning up..."
docker-compose stop
docker-compose rm -f
: "Finished!"
exit $exit_code
}
trap cleanup INT TERM EXIT
set -ex
: 'Building docker images'
docker-compose build
: 'Running linter'
docker-compose run --rm test yarn run lint | gergich capture eslint -
gergich status
: 'Running tests'
docker-compose run --rm test yarn run test:coverage
: 'Starting containers'
docker-compose up -d
: 'Publishing coverage'
docker cp $(docker-compose ps -q test):/usr/src/app/coverage/. coverage
: 'Reporting coverage to gergich'
message=$(generate_coverage_message)
gergich message "$message"
: 'Publishing to gergich'
[[ ${GERGICH_KEY} || ${GERGICH_USER} ]] && gergich publish

View File

@ -0,0 +1,112 @@
{
"el": {
"reviewed_percentage": "0%",
"completed": "32%",
"untranslated_words": 223,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 22,
"translated_words": 24,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 46
},
"hy": {
"reviewed_percentage": "0%",
"completed": "26%",
"untranslated_words": 227,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 18,
"translated_words": 20,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 50
},
"fa_IR": {
"reviewed_percentage": "0%",
"completed": "34%",
"untranslated_words": 220,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 23,
"translated_words": 27,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 45
},
"nn": {
"reviewed_percentage": "0%",
"completed": "32%",
"untranslated_words": 223,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 22,
"translated_words": 24,
"last_update": "2017-12-20 10:09:46",
"untranslated_entities": 46
},
"tr": {
"reviewed_percentage": "0%",
"completed": "49%",
"untranslated_words": 186,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 33,
"translated_words": 61,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 35
},
"ko": {
"reviewed_percentage": "0%",
"completed": "26%",
"untranslated_words": 227,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 18,
"translated_words": 20,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 50
},
"hu": {
"reviewed_percentage": "0%",
"completed": "34%",
"untranslated_words": 220,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 23,
"translated_words": 27,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 45
},
"uk_UA": {
"reviewed_percentage": "0%",
"completed": "32%",
"untranslated_words": 222,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 22,
"translated_words": 25,
"last_update": "2017-12-14 12:48:00",
"untranslated_entities": 46
},
"en": {
"reviewed_percentage": "0%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 0
},
"he": {
"reviewed_percentage": "12%",
"completed": "97%",
"untranslated_words": 8,
"last_commiter": "canvastransifex",
"reviewed": 8,
"translated_entities": 66,
"translated_words": 239,
"last_update": "2017-12-12 14:40:53",
"untranslated_entities": 2
}
}

View File

@ -0,0 +1,255 @@
{
"en": {
"reviewed_percentage": "0%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "canvastransifex",
"reviewed": 0,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-12 14:40:52",
"untranslated_entities": 0
},
"zh": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:30:20",
"untranslated_entities": 0
},
"is": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:23",
"untranslated_entities": 0
},
"it": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:18",
"untranslated_entities": 0
},
"ar": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:30:29",
"untranslated_entities": 0
},
"es": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:22",
"untranslated_entities": 0
},
"ru": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:28",
"untranslated_entities": 0
},
"nl": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:59",
"untranslated_entities": 0
},
"pt": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:39",
"untranslated_entities": 0
},
"no": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:58",
"untranslated_entities": 0
},
"en_AU": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:30:05",
"untranslated_entities": 0
},
"en_GB": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:54",
"untranslated_entities": 0
},
"fr": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:47",
"untranslated_entities": 0
},
"zh_HK": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:30:25",
"untranslated_entities": 0
},
"pt_BR": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:33",
"untranslated_entities": 0
},
"de": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:36",
"untranslated_entities": 0
},
"ht": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:29",
"untranslated_entities": 0
},
"da": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:30:10",
"untranslated_entities": 0
},
"ja": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:12",
"untranslated_entities": 0
},
"fr_CA": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:42",
"untranslated_entities": 0
},
"mi": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:29:05",
"untranslated_entities": 0
},
"sv": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:15",
"untranslated_entities": 0
},
"pl": {
"reviewed_percentage": "100%",
"completed": "100%",
"untranslated_words": 0,
"last_commiter": "ZabOperations",
"reviewed": 68,
"translated_entities": 68,
"translated_words": 247,
"last_update": "2017-12-26 18:28:47",
"untranslated_entities": 0
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "إضافة قائمة مهام"
},
"all_items_loaded_aa256183": {
"message": "تم تحميل كافة العناصر"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "هل أنت متأكد أنك تريد حذف عنصر المخطط هذا؟"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } مكتمل"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } غير مكتمل"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "بداية سجل قائمة المهام الخاصة بك"
},
"canvas_planner_98ed106": {
"message": "مخطِط Canvas"
},
"close_d634289d": {
"message": "إغلاق"
},
"close_opportunities_popover_f4e50551": {
"message": "إغلاق الصفحات المنبثقة العلوية للفرص"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# فرص}\n one {# فرصة}\n other {# فرص}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {إظهار # عنصر مكتمل}\n other {إظهار # عناصر مكتملة}\n}"
},
"course_8a63b4a3": {
"message": "المساق"
},
"course_to_do_bcbbab54": {
"message": "{ course } قائمة مهام"
},
"date_at_time_dbdb1b99": {
"message": "{ date } في { time }"
},
"date_ee500367": {
"message": "التاريخ"
},
"date_is_required_8660ec22": {
"message": "التاريخ مطلوب"
},
"delete_a6efa79d": {
"message": "حذف"
},
"details_98a31b68": {
"message": "التفاصيل"
},
"dismiss_opportunityname_5995176f": {
"message": "تجاهل { opportunityName }"
},
"due_5d7267be": {
"message": "تاريخ الاستحقاق:"
},
"due_date_8ce289b6": {
"message": "تاريخ الاستحقاق: { date }"
},
"edit_title_72e5a21e": {
"message": "تحرير { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "حدث خطأ في تحميل المزيد من العناصر"
},
"error_loading_past_items_2881dbb1": {
"message": "حدث خطأ في تحميل العناصر السابقة"
},
"excused_cf8792eb": {
"message": "غير إجباري"
},
"failed_to_delete_to_do_64edff49": {
"message": "تعذر حذف قائمة مهام"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "تعذر الحصول على نشاط جديد"
},
"failed_to_load_opportunities_52ab6404": {
"message": "تعذر تحميل الفرص"
},
"failed_to_save_to_do_ddc7503b": {
"message": "تعذر حفظ قائمة مهام"
},
"feedback_6dcc1991": {
"message": "التعليقات"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "اذهب إلى طريقة عرض بطاقة لوحة المعلومات"
},
"graded_25cd3fcd": {
"message": "تم منح الدرجات"
},
"late_2be42b88": {
"message": "متأخر"
},
"load_more_a36f9cf9": {
"message": "تحميل المزيد"
},
"load_prior_dates_f2b0f6f0": {
"message": "تحميل تواريخ سابقة"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "{ count, plural,\n =0{# عناصر}\n one {# عنصر}\n other {# عناصر تم تحميلها}\n}"
},
"loading_25990131": {
"message": "يتم الآن التحميل..."
},
"loading_past_items_ca499e75": {
"message": "جارٍ تحميل العناصر السابقة"
},
"loading_planner_items_947a813d": {
"message": "جارٍ تحميل عناصر المخطِط"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "يبدو أنه لا يوجد أي شيء هنا"
},
"missing_1a256b3b": {
"message": "مفقود"
},
"missing_items_for_5ef30cea": {
"message": "عناصر مفقودة لـ "
},
"new_activity_8b84847d": {
"message": "نشاط جديد"
},
"new_activity_for_ac84f6e6": {
"message": "نشاط جديد لـ "
},
"next_month_749b1778": {
"message": "الشهر القادم"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "لم يتم تعيين تواريخ الاستحقاق"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "لا توجد \"قوائم مهام\" في هذا اليوم حتى الآن."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "لا يوجد شيء جديد يستدعي الاهتمام."
},
"optional_add_course_ef0d70fc": {
"message": "اختياري: إضافة مساق"
},
"points_bceb5005": {
"message": "نقاط"
},
"points_points_63e59cce": {
"message": "{ points } نقاط"
},
"previous_month_bb1e3c84": {
"message": "الشهر السابق"
},
"pts_699bd9aa": {
"message": "نقاط"
},
"replies_4a8577c8": {
"message": "ردود"
},
"save_11a80ec3": {
"message": "حفظ"
},
"submitted_318fad53": {
"message": "تم الإرسال"
},
"task_16b0ef38": {
"message": "مهمة"
},
"title_ee03d132": {
"message": "العنوان"
},
"title_is_required_6ddcab69": {
"message": "العنوان مطلوب"
},
"to_do_1d554f36": {
"message": "قائمة مهام"
},
"today_76e10f9c": {
"message": "اليوم"
},
"today_at_date_8ac30d6": {
"message": "اليوم الساعة { date }"
},
"tomorrow_9a6c9a00": {
"message": "غدًا"
},
"tomorrow_at_date_b53f2cf1": {
"message": "غدًا الساعة { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "يتعذر وضع علامة مكتمل."
},
"yesterday_at_date_1aa6d18e": {
"message": "أمس الساعة { date }"
},
"yesterday_c6bd6abf": {
"message": "أمس"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "لقد قمت بالتمرير للخلف إلى أول قائمة مهام خاصة بك!"
}
}

View File

@ -0,0 +1,35 @@
{
"close_d634289d": {
"message": "Затвори"
},
"course_8a63b4a3": {
"message": "Курс"
},
"date_ee500367": {
"message": "Дата"
},
"delete_a6efa79d": {
"message": "Изтрий"
},
"details_98a31b68": {
"message": "Детайли"
},
"due_5d7267be": {
"message": "До:"
},
"load_more_a36f9cf9": {
"message": "Зареди още"
},
"loading_25990131": {
"message": "Зареждане..."
},
"points_bceb5005": {
"message": "точки"
},
"save_11a80ec3": {
"message": "Запази"
},
"title_ee03d132": {
"message": "Заглавие"
}
}

View File

@ -0,0 +1,26 @@
{
"close_d634289d": {
"message": "Zavřít"
},
"course_8a63b4a3": {
"message": "Kurz"
},
"date_ee500367": {
"message": "Datum"
},
"delete_a6efa79d": {
"message": "Smazat"
},
"details_98a31b68": {
"message": "Detaily"
},
"loading_25990131": {
"message": "Nahrávám..."
},
"save_11a80ec3": {
"message": "Uložit"
},
"title_ee03d132": {
"message": "Nadpis"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Tilføj til opgaveliste"
},
"all_items_loaded_aa256183": {
"message": "Alle elementer er indlæst"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Er du sikker på, du vil slette dette planlægningselement?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } er komplet"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } er ufuldstændig"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Begyndelsen af din Opgaveliste-historik"
},
"canvas_planner_98ed106": {
"message": "Canvas-planlægger"
},
"close_d634289d": {
"message": "Luk"
},
"close_opportunities_popover_f4e50551": {
"message": "Luk muligheder-popover"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# muligheder}\n one {# mulighed}\n other {# muligheder}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Vis # afsluttet emne}\n other {Vis # afsluttede emner}\n}"
},
"course_8a63b4a3": {
"message": "Fag"
},
"course_to_do_bcbbab54": {
"message": "{ course } OPGAVELISTE"
},
"date_at_time_dbdb1b99": {
"message": "{ date } ved { time }"
},
"date_ee500367": {
"message": "Dato"
},
"date_is_required_8660ec22": {
"message": "Dato er påkrævet"
},
"delete_a6efa79d": {
"message": "Slet"
},
"details_98a31b68": {
"message": "Nærmere oplysninger"
},
"dismiss_opportunityname_5995176f": {
"message": "Afvis { opportunityName }"
},
"due_5d7267be": {
"message": "Forfalder:"
},
"due_date_8ce289b6": {
"message": "FORFALDER: { date }"
},
"edit_title_72e5a21e": {
"message": "Rediger { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Fejl ved indlæsning af flere elementer"
},
"error_loading_past_items_2881dbb1": {
"message": "Fejl ved indlæsning af forrige elementer"
},
"excused_cf8792eb": {
"message": "Undskyldt"
},
"failed_to_delete_to_do_64edff49": {
"message": "Kunne ikke slette opgave"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Kunne ikke hente ny aktivitet"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Kunne ikke indlæse muligheder"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Kunne ikke gemme opgave"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Gå til forsidens kortvisning"
},
"graded_25cd3fcd": {
"message": "Bedømt"
},
"late_2be42b88": {
"message": "Sen"
},
"load_more_a36f9cf9": {
"message": "Indlæs mere"
},
"load_prior_dates_f2b0f6f0": {
"message": "Indlæs tidligere datoer"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "indlæst { count, plural,\n =0{# elementer}\n one {# element}\n other {# elementer}\n}"
},
"loading_25990131": {
"message": "Indlæser ..."
},
"loading_past_items_ca499e75": {
"message": "Indlæser forrige elementer"
},
"loading_planner_items_947a813d": {
"message": "Indlæser planlægningselementer"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Det ser ud som om, der ikke er noget her"
},
"missing_1a256b3b": {
"message": "Mangler"
},
"missing_items_for_5ef30cea": {
"message": "Manglende elementer for "
},
"new_activity_8b84847d": {
"message": "Ny aktivitet"
},
"new_activity_for_ac84f6e6": {
"message": "Ny aktivitet for "
},
"next_month_749b1778": {
"message": "Næste måned"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Ingen afleveringsdatoer tildelt"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Ingenting på opgavelisten for denne dag endnu."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Intet nyt har brug for opmærksomhed."
},
"optional_add_course_ef0d70fc": {
"message": "Valgfrit: Tilføj fag"
},
"points_bceb5005": {
"message": "point"
},
"points_points_63e59cce": {
"message": "{ points } point"
},
"previous_month_bb1e3c84": {
"message": "Forrige måned"
},
"pts_699bd9aa": {
"message": "point"
},
"replies_4a8577c8": {
"message": "Svar"
},
"save_11a80ec3": {
"message": "Gem"
},
"submitted_318fad53": {
"message": "Indsendt"
},
"task_16b0ef38": {
"message": "Opgave"
},
"title_ee03d132": {
"message": "Titel"
},
"title_is_required_6ddcab69": {
"message": "titel er påkrævet"
},
"to_do_1d554f36": {
"message": "Opgaveliste"
},
"today_76e10f9c": {
"message": "I dag"
},
"today_at_date_8ac30d6": {
"message": "I dag kl. { date }"
},
"tomorrow_9a6c9a00": {
"message": "I morgen"
},
"tomorrow_at_date_b53f2cf1": {
"message": "I morgen kl. { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Kan ikke markeres som færdig."
},
"yesterday_at_date_1aa6d18e": {
"message": "I går kl. { date }"
},
"yesterday_c6bd6abf": {
"message": "I går"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Du har rullet tilbage til din allerførste opgave på Opgavelisten!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Aufgabe hinzufügen"
},
"all_items_loaded_aa256183": {
"message": "Alle Elemente geladen"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Möchten Sie diesen Plan wirklich löschen?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } Vollständig"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } Unvollständig"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Beginn Ihrer Aufgabenhistorie"
},
"canvas_planner_98ed106": {
"message": "Canvas-Plan"
},
"close_d634289d": {
"message": "Schließen"
},
"close_opportunities_popover_f4e50551": {
"message": "Chancen-Popover schließen"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# Gelegenheiten}\n one {# Gelegenheit}\n other {# Gelegenheiten}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {# Fertiges Element anzeigen }\n other {# Fertiges Element anzeigen}\n}"
},
"course_8a63b4a3": {
"message": "Kurs"
},
"course_to_do_bcbbab54": {
"message": "{ course } ZU ERLEDIGEN"
},
"date_at_time_dbdb1b99": {
"message": "{ date } um { time }"
},
"date_ee500367": {
"message": "Datum"
},
"date_is_required_8660ec22": {
"message": "Datum ist erforderlich"
},
"delete_a6efa79d": {
"message": "Löschen"
},
"details_98a31b68": {
"message": "Details"
},
"dismiss_opportunityname_5995176f": {
"message": "Verwerfen { opportunityName }"
},
"due_5d7267be": {
"message": "Fällig:"
},
"due_date_8ce289b6": {
"message": "FÄLLIG: { date }"
},
"edit_title_72e5a21e": {
"message": "Ändern { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Fehler beim Laden weiterer Objekte"
},
"error_loading_past_items_2881dbb1": {
"message": "Fehler beim Laden bisheriger Objekte"
},
"excused_cf8792eb": {
"message": "Entschuldigt"
},
"failed_to_delete_to_do_64edff49": {
"message": "Löschen der Aufgabe fehlgeschlagen"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Fehler beim Abrufen einer neuen Tätigkeit"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Laden von Chancen fehlgeschlagen"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Speichern der Aufgabe fehlgeschlagen"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Gehen Sie zur Dashboard-Kartenansicht."
},
"graded_25cd3fcd": {
"message": "Benotet"
},
"late_2be42b88": {
"message": "Verspätet"
},
"load_more_a36f9cf9": {
"message": "Mehr laden"
},
"load_prior_dates_f2b0f6f0": {
"message": "Frühere Termine laden"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Geladene { count, plural,\n =0{# Objekte}\n one {# Objekt}\n other {# Objekte}\n}"
},
"loading_25990131": {
"message": "Wird geladen ..."
},
"loading_past_items_ca499e75": {
"message": "Spätere Elemente werden geladen"
},
"loading_planner_items_947a813d": {
"message": "Planungselemente werden geladen."
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Sieht so aus, als wäre hier nichts."
},
"missing_1a256b3b": {
"message": "Fehlt"
},
"missing_items_for_5ef30cea": {
"message": "Fehlende Elemente für "
},
"new_activity_8b84847d": {
"message": "Neue Tätigkeit"
},
"new_activity_for_ac84f6e6": {
"message": "Neue Aktivität für "
},
"next_month_749b1778": {
"message": "Nächster Monat"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Keine Fälligkeitsdaten zugewiesen"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Noch keine Aufgaben für diesen Tag"
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nichts Neues, das beachtet werden muss"
},
"optional_add_course_ef0d70fc": {
"message": "Optional: Kurs hinzufügen"
},
"points_bceb5005": {
"message": "Punkte"
},
"points_points_63e59cce": {
"message": "{ points } Punkte"
},
"previous_month_bb1e3c84": {
"message": "Vorheriger Monat"
},
"pts_699bd9aa": {
"message": "Pkte."
},
"replies_4a8577c8": {
"message": "Antworten"
},
"save_11a80ec3": {
"message": "Speichern"
},
"submitted_318fad53": {
"message": "Abgegeben"
},
"task_16b0ef38": {
"message": "Aufgabe"
},
"title_ee03d132": {
"message": "Titel"
},
"title_is_required_6ddcab69": {
"message": "Titel ist erforderlich"
},
"to_do_1d554f36": {
"message": "Zu erledigen"
},
"today_76e10f9c": {
"message": "Heute"
},
"today_at_date_8ac30d6": {
"message": "Heute um { date }"
},
"tomorrow_9a6c9a00": {
"message": "Morgen"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Morgen um { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Kann nicht als fertiggestellt markiert werden."
},
"yesterday_at_date_1aa6d18e": {
"message": "Gestern um { date }"
},
"yesterday_c6bd6abf": {
"message": "Gestern"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Sie haben zurückgeblättert zu ihrer allerersten Aufgabe!"
}
}

View File

@ -0,0 +1,68 @@
{
"close_d634289d": {
"message": "Κλείσιμο"
},
"course_8a63b4a3": {
"message": "Μάθημα"
},
"date_ee500367": {
"message": "Ημερομηνία"
},
"delete_a6efa79d": {
"message": "Διαγραφή"
},
"details_98a31b68": {
"message": "Λεπτομέρειες"
},
"due_5d7267be": {
"message": "Καταληκτική Ημερ/νία:"
},
"excused_cf8792eb": {
"message": "Δικαιολογημένος/η"
},
"graded_25cd3fcd": {
"message": "Βαθμολογημένα"
},
"late_2be42b88": {
"message": "Πρόσφατο/α"
},
"load_more_a36f9cf9": {
"message": "Φόρτωση περισσότερων"
},
"loading_25990131": {
"message": "Φόρτωση..."
},
"missing_1a256b3b": {
"message": "Απομένει"
},
"points_bceb5005": {
"message": "Μόρια"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Απαντήσεις"
},
"save_11a80ec3": {
"message": "Αποθήκευση"
},
"submitted_318fad53": {
"message": "Υποβλήθηκαν"
},
"title_ee03d132": {
"message": "Τίτλος"
},
"to_do_1d554f36": {
"message": "Υποχρεώσεις"
},
"today_76e10f9c": {
"message": "Σήμερα"
},
"tomorrow_9a6c9a00": {
"message": "Αύριο"
},
"yesterday_c6bd6abf": {
"message": "Χτες"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Add To Do"
},
"all_items_loaded_aa256183": {
"message": "All items loaded"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Are you sure you want to delete this planner item?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } is complete"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } is incomplete"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Beginning of Your To-Do History"
},
"canvas_planner_98ed106": {
"message": "Canvas Planner"
},
"close_d634289d": {
"message": "Close"
},
"close_opportunities_popover_f4e50551": {
"message": "Close opportunities popover"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# opportunities}\n one {# opportunity}\n other {# opportunities}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Show # completed item}\n other {Show # completed items}\n}"
},
"course_8a63b4a3": {
"message": "Course"
},
"course_to_do_bcbbab54": {
"message": "{ course } TO DO"
},
"date_at_time_dbdb1b99": {
"message": "{ date } at { time }"
},
"date_ee500367": {
"message": "Date"
},
"date_is_required_8660ec22": {
"message": "Date is required"
},
"delete_a6efa79d": {
"message": "Delete"
},
"details_98a31b68": {
"message": "Details"
},
"dismiss_opportunityname_5995176f": {
"message": "Dismiss { opportunityName }"
},
"due_5d7267be": {
"message": "Due:"
},
"due_date_8ce289b6": {
"message": "DUE: { date }"
},
"edit_title_72e5a21e": {
"message": "Edit { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Error loading more items"
},
"error_loading_past_items_2881dbb1": {
"message": "Error loading past items"
},
"excused_cf8792eb": {
"message": "Excused"
},
"failed_to_delete_to_do_64edff49": {
"message": "Failed to delete to do"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Failed to get new activity"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Failed to load opportunities"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Failed to save to do"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Go to Dashboard Card View"
},
"graded_25cd3fcd": {
"message": "Marked"
},
"late_2be42b88": {
"message": "Late"
},
"load_more_a36f9cf9": {
"message": "Load more"
},
"load_prior_dates_f2b0f6f0": {
"message": "Load prior dates"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Loaded { count, plural,\n =0{# items}\n one {# item}\n other {# items}\n}"
},
"loading_25990131": {
"message": "Loading..."
},
"loading_past_items_ca499e75": {
"message": "Loading past items"
},
"loading_planner_items_947a813d": {
"message": "Loading planner items"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Looks like there isn''t anything here"
},
"missing_1a256b3b": {
"message": "Missing"
},
"missing_items_for_5ef30cea": {
"message": "Missing items for "
},
"new_activity_8b84847d": {
"message": "New Activity"
},
"new_activity_for_ac84f6e6": {
"message": "New activity for "
},
"next_month_749b1778": {
"message": "Next Month"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "No Due Dates Assigned"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "No \"To-Do''s\" for this day yet."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nothing new needs attention."
},
"optional_add_course_ef0d70fc": {
"message": "Optional: Add Course"
},
"points_bceb5005": {
"message": "points"
},
"points_points_63e59cce": {
"message": "{ points } points"
},
"previous_month_bb1e3c84": {
"message": "Previous Month"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Replies"
},
"save_11a80ec3": {
"message": "Save"
},
"submitted_318fad53": {
"message": "Submitted"
},
"task_16b0ef38": {
"message": "Task"
},
"title_ee03d132": {
"message": "Title"
},
"title_is_required_6ddcab69": {
"message": "title is required"
},
"to_do_1d554f36": {
"message": "To Do"
},
"today_76e10f9c": {
"message": "Today"
},
"today_at_date_8ac30d6": {
"message": "Today at { date }"
},
"tomorrow_9a6c9a00": {
"message": "Tomorrow"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Tomorrow at { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Unable to mark as complete."
},
"yesterday_at_date_1aa6d18e": {
"message": "Yesterday at { date }"
},
"yesterday_c6bd6abf": {
"message": "Yesterday"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "You''ve scrolled back to your very first To-Do!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Add To Do"
},
"all_items_loaded_aa256183": {
"message": "All items loaded"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Are you sure you want to delete this planner item?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } is complete"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } is incomplete"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Beginning of Your To-Do History"
},
"canvas_planner_98ed106": {
"message": "Canvas Planner"
},
"close_d634289d": {
"message": "Close"
},
"close_opportunities_popover_f4e50551": {
"message": "Close opportunities popover"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# opportunities}\n one {# opportunity}\n other {# opportunities}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Show # completed item}\n other {Show # completed items}\n}"
},
"course_8a63b4a3": {
"message": "Course"
},
"course_to_do_bcbbab54": {
"message": "{ course } TO DO"
},
"date_at_time_dbdb1b99": {
"message": "{ date } at { time }"
},
"date_ee500367": {
"message": "Date"
},
"date_is_required_8660ec22": {
"message": "Date is required"
},
"delete_a6efa79d": {
"message": "Delete"
},
"details_98a31b68": {
"message": "Details"
},
"dismiss_opportunityname_5995176f": {
"message": "Dismiss { opportunityName }"
},
"due_5d7267be": {
"message": "Due:"
},
"due_date_8ce289b6": {
"message": "DUE: { date }"
},
"edit_title_72e5a21e": {
"message": "Edit { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Error loading more items"
},
"error_loading_past_items_2881dbb1": {
"message": "Error loading past items"
},
"excused_cf8792eb": {
"message": "Excused"
},
"failed_to_delete_to_do_64edff49": {
"message": "Failed to delete to do"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Failed to get new activity"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Failed to load opportunities"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Failed to save to do"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Go to Dashboard Card View"
},
"graded_25cd3fcd": {
"message": "Graded"
},
"late_2be42b88": {
"message": "Late"
},
"load_more_a36f9cf9": {
"message": "Load more"
},
"load_prior_dates_f2b0f6f0": {
"message": "Load prior dates"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Loaded { count, plural,\n =0{# items}\n one {# item}\n other {# items}\n}"
},
"loading_25990131": {
"message": "Loading..."
},
"loading_past_items_ca499e75": {
"message": "Loading past items"
},
"loading_planner_items_947a813d": {
"message": "Loading planner items"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Looks like there isn''t anything here"
},
"missing_1a256b3b": {
"message": "Missing"
},
"missing_items_for_5ef30cea": {
"message": "Missing items for "
},
"new_activity_8b84847d": {
"message": "New Activity"
},
"new_activity_for_ac84f6e6": {
"message": "New activity for "
},
"next_month_749b1778": {
"message": "Next Month"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "No Due Dates Assigned"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "No ''To-dos'' for this day yet."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nothing new needs attention."
},
"optional_add_course_ef0d70fc": {
"message": "Optional: Add course"
},
"points_bceb5005": {
"message": "points"
},
"points_points_63e59cce": {
"message": "{ points } points"
},
"previous_month_bb1e3c84": {
"message": "Previous Month"
},
"pts_699bd9aa": {
"message": "Pts"
},
"replies_4a8577c8": {
"message": "Replies"
},
"save_11a80ec3": {
"message": "Save"
},
"submitted_318fad53": {
"message": "Submitted"
},
"task_16b0ef38": {
"message": "Task"
},
"title_ee03d132": {
"message": "Title"
},
"title_is_required_6ddcab69": {
"message": "title is required"
},
"to_do_1d554f36": {
"message": "To do"
},
"today_76e10f9c": {
"message": "Today"
},
"today_at_date_8ac30d6": {
"message": "Today at { date }"
},
"tomorrow_9a6c9a00": {
"message": "Tomorrow"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Tomorrow at { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Unable to mark as complete."
},
"yesterday_at_date_1aa6d18e": {
"message": "Yesterday at { date }"
},
"yesterday_c6bd6abf": {
"message": "Yesterday"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "You''ve scrolled back to your very first To-Do!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Add To Do"
},
"all_items_loaded_aa256183": {
"message": "All items loaded"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Are you sure you want to delete this planner item?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } is complete"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } is incomplete"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Beginning of Your To-Do History"
},
"canvas_planner_98ed106": {
"message": "Canvas Planner"
},
"close_d634289d": {
"message": "Close"
},
"close_opportunities_popover_f4e50551": {
"message": "Close opportunities popover"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# opportunities}\n one {# opportunity}\n other {# opportunities}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Show # completed item}\n other {Show # completed items}\n}"
},
"course_8a63b4a3": {
"message": "Course"
},
"course_to_do_bcbbab54": {
"message": "{ course } TO DO"
},
"date_at_time_dbdb1b99": {
"message": "{ date } at { time }"
},
"date_ee500367": {
"message": "Date"
},
"date_is_required_8660ec22": {
"message": "Date is required"
},
"delete_a6efa79d": {
"message": "Delete"
},
"details_98a31b68": {
"message": "Details"
},
"dismiss_opportunityname_5995176f": {
"message": "Dismiss { opportunityName }"
},
"due_5d7267be": {
"message": "Due:"
},
"due_date_8ce289b6": {
"message": "DUE: { date }"
},
"edit_title_72e5a21e": {
"message": "Edit { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Error loading more items"
},
"error_loading_past_items_2881dbb1": {
"message": "Error loading past items"
},
"excused_cf8792eb": {
"message": "Excused"
},
"failed_to_delete_to_do_64edff49": {
"message": "Failed to delete to do"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Failed to get new activity"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Failed to load opportunities"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Failed to save to do"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Go to Dashboard Card View"
},
"graded_25cd3fcd": {
"message": "Graded"
},
"late_2be42b88": {
"message": "Late"
},
"load_more_a36f9cf9": {
"message": "Load more"
},
"load_prior_dates_f2b0f6f0": {
"message": "Load prior dates"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Loaded { count, plural,\n =0 {# items}\n one {# item}\n other {# items}\n}"
},
"loading_25990131": {
"message": "Loading..."
},
"loading_past_items_ca499e75": {
"message": "Loading past items"
},
"loading_planner_items_947a813d": {
"message": "Loading planner items"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Looks like there isn''t anything here"
},
"missing_1a256b3b": {
"message": "Missing"
},
"missing_items_for_5ef30cea": {
"message": "Missing items for "
},
"new_activity_8b84847d": {
"message": "New Activity"
},
"new_activity_for_ac84f6e6": {
"message": "New activity for "
},
"next_month_749b1778": {
"message": "Next Month"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "No Due Dates Assigned"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "No \"To-Do''s\" for this day yet."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nothing new needs attention."
},
"optional_add_course_ef0d70fc": {
"message": "Optional: Add Course"
},
"points_bceb5005": {
"message": "points"
},
"points_points_63e59cce": {
"message": "{ points } points"
},
"previous_month_bb1e3c84": {
"message": "Previous Month"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Replies"
},
"save_11a80ec3": {
"message": "Save"
},
"submitted_318fad53": {
"message": "Submitted"
},
"task_16b0ef38": {
"message": "Task"
},
"title_ee03d132": {
"message": "Title"
},
"title_is_required_6ddcab69": {
"message": "title is required"
},
"to_do_1d554f36": {
"message": "To Do"
},
"today_76e10f9c": {
"message": "Today"
},
"today_at_date_8ac30d6": {
"message": "Today at { date }"
},
"tomorrow_9a6c9a00": {
"message": "Tomorrow"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Tomorrow at { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Unable to mark as complete."
},
"yesterday_at_date_1aa6d18e": {
"message": "Yesterday at { date }"
},
"yesterday_c6bd6abf": {
"message": "Yesterday"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "You''ve scrolled back to your very first To-Do!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Agregar actividad por hacer"
},
"all_items_loaded_aa256183": {
"message": "Todos los elementos cargados"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "¿Está seguro de que desea eliminar este elemento del programador?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } está completa"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } está incompleta"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Inicio de su Historial de cosas por hacer"
},
"canvas_planner_98ed106": {
"message": "Planificador de Canvas"
},
"close_d634289d": {
"message": "Cerrar"
},
"close_opportunities_popover_f4e50551": {
"message": "Cerrar menú popover de oportunidades"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# oportunidades}\n one {# oportunidad}\n other {# oportunidades}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Mostrar # de elementos completados}\n other {Mostrar # de elementos completados}\n}"
},
"course_8a63b4a3": {
"message": "Curso"
},
"course_to_do_bcbbab54": {
"message": "{ course } POR HACER"
},
"date_at_time_dbdb1b99": {
"message": "{ date } en { time }"
},
"date_ee500367": {
"message": "Fecha"
},
"date_is_required_8660ec22": {
"message": "Se requiere la fecha"
},
"delete_a6efa79d": {
"message": "Eliminar"
},
"details_98a31b68": {
"message": "Detalles"
},
"dismiss_opportunityname_5995176f": {
"message": "Descartar { opportunityName }"
},
"due_5d7267be": {
"message": "Hora límite:"
},
"due_date_8ce289b6": {
"message": "VENCIMIENTO: { date }"
},
"edit_title_72e5a21e": {
"message": "Editar { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Error al cargar más elementos"
},
"error_loading_past_items_2881dbb1": {
"message": "Error al cargar elementos anteriores"
},
"excused_cf8792eb": {
"message": "Justificado"
},
"failed_to_delete_to_do_64edff49": {
"message": "Error al eliminar tareas pendientes"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Error al obtener nueva actividad"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Error al cargar oportunidades"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Error al guardar tareas pendientes"
},
"feedback_6dcc1991": {
"message": "Retroalimentación"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Vaya a la vista de la tarjeta del tablero de control"
},
"graded_25cd3fcd": {
"message": "Calificado"
},
"late_2be42b88": {
"message": "Atrasado"
},
"load_more_a36f9cf9": {
"message": "Cargar más"
},
"load_prior_dates_f2b0f6f0": {
"message": "CarCargar fechas anteriores"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Cargó { count, plural,\n =0{# elementos}\n one {# elemento}\n other {# elementos}\n}"
},
"loading_25990131": {
"message": "Cargando..."
},
"loading_past_items_ca499e75": {
"message": "Cargando elementos anteriores"
},
"loading_planner_items_947a813d": {
"message": "Cargando elementos del planificador"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Parece que no hay nada aquí"
},
"missing_1a256b3b": {
"message": "Faltante"
},
"missing_items_for_5ef30cea": {
"message": "Elementos faltantes para "
},
"new_activity_8b84847d": {
"message": "Nueva actividad"
},
"new_activity_for_ac84f6e6": {
"message": "Nueva actividad para "
},
"next_month_749b1778": {
"message": "Mes siguiente"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "No hay fechas límite asignadas"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Aún sin “tareas pendientes” para este día."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "No hay nada nuevo que necesite atención."
},
"optional_add_course_ef0d70fc": {
"message": "Opcional: Agregar curso"
},
"points_bceb5005": {
"message": "puntos"
},
"points_points_63e59cce": {
"message": "{ points } puntos"
},
"previous_month_bb1e3c84": {
"message": "Mes anterior"
},
"pts_699bd9aa": {
"message": "ptos."
},
"replies_4a8577c8": {
"message": "Respuestas"
},
"save_11a80ec3": {
"message": "Guardar"
},
"submitted_318fad53": {
"message": "Presentado"
},
"task_16b0ef38": {
"message": "Tarea"
},
"title_ee03d132": {
"message": "Título"
},
"title_is_required_6ddcab69": {
"message": "se requiere un título"
},
"to_do_1d554f36": {
"message": "Por hacer"
},
"today_76e10f9c": {
"message": "Hoy"
},
"today_at_date_8ac30d6": {
"message": "Hoy a las { date }"
},
"tomorrow_9a6c9a00": {
"message": "Mañana"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Mañana a las { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "No se puede marcar como completada."
},
"yesterday_at_date_1aa6d18e": {
"message": "Ayer a las { date }"
},
"yesterday_c6bd6abf": {
"message": "Ayer"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "¡Ha retrocedido a su primer elemento por hacer!"
}
}

View File

@ -0,0 +1,71 @@
{
"close_d634289d": {
"message": "بستن"
},
"course_8a63b4a3": {
"message": "درس"
},
"date_ee500367": {
"message": "تاریخ"
},
"delete_a6efa79d": {
"message": "حذف"
},
"details_98a31b68": {
"message": "اطلاعات"
},
"due_5d7267be": {
"message": "مهلت:"
},
"excused_cf8792eb": {
"message": "معاف شد"
},
"graded_25cd3fcd": {
"message": "نمره گذاری شده"
},
"late_2be42b88": {
"message": "با تأخیر"
},
"load_more_a36f9cf9": {
"message": "بارگذاری بیشتر"
},
"loading_25990131": {
"message": "در حال بارگذاری..."
},
"missing_1a256b3b": {
"message": "جا افتاده"
},
"next_month_749b1778": {
"message": "ماه بعد"
},
"points_bceb5005": {
"message": "امتیازها"
},
"previous_month_bb1e3c84": {
"message": "ماه قبل"
},
"pts_699bd9aa": {
"message": "امتیازها"
},
"save_11a80ec3": {
"message": "ذخیره سازی"
},
"submitted_318fad53": {
"message": "ارسال شد"
},
"title_ee03d132": {
"message": "عنوان"
},
"to_do_1d554f36": {
"message": "برای انجام"
},
"today_76e10f9c": {
"message": "امروز"
},
"tomorrow_9a6c9a00": {
"message": "فردا"
},
"yesterday_c6bd6abf": {
"message": "دیروز"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Ajouter une tâche"
},
"all_items_loaded_aa256183": {
"message": "Tous les éléments sont chargés"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Êtes-vous certain de vouloir supprimer cet élément de planification?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } est terminée"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } est incomplète"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Début de votre historique des tâches À faire"
},
"canvas_planner_98ed106": {
"message": "Planificateur Canvas"
},
"close_d634289d": {
"message": "Fermer"
},
"close_opportunities_popover_f4e50551": {
"message": "Fermer la boîte contextuelle des opportunités"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# possibilités}\n one {# possibilité}\n other {# possibilités}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Afficher # élément terminé}\n other {Afficher # éléments terminés}\n}"
},
"course_8a63b4a3": {
"message": "Cours"
},
"course_to_do_bcbbab54": {
"message": "{ course } À FAIRE"
},
"date_at_time_dbdb1b99": {
"message": "{ date } à { time }"
},
"date_ee500367": {
"message": "Date"
},
"date_is_required_8660ec22": {
"message": "La date est obligatoire"
},
"delete_a6efa79d": {
"message": "Supprimer"
},
"details_98a31b68": {
"message": "Détails"
},
"dismiss_opportunityname_5995176f": {
"message": "Ignorer { opportunityName }"
},
"due_5d7267be": {
"message": "Échéance :"
},
"due_date_8ce289b6": {
"message": "DÛ : { date }"
},
"edit_title_72e5a21e": {
"message": "Modifier { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Erreur lors du chargement de plus déléments"
},
"error_loading_past_items_2881dbb1": {
"message": "Erreur lors du chargement des éléments passés"
},
"excused_cf8792eb": {
"message": "Exempté"
},
"failed_to_delete_to_do_64edff49": {
"message": "Échec lors de la suppression des « activités à faire »"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Échec lors de l''obtention de la nouvelle activité"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Échec lors du chargement des opportunités"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Échec lors de lenregistrement des « activités à faire »"
},
"feedback_6dcc1991": {
"message": "Rétroaction"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Aller à la vue de carte du tableau de bord"
},
"graded_25cd3fcd": {
"message": "Noté"
},
"late_2be42b88": {
"message": "En retard"
},
"load_more_a36f9cf9": {
"message": "En charger plus"
},
"load_prior_dates_f2b0f6f0": {
"message": "Charger les dates précédentes"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Chargé { count, plural,\n =0{# éléments}\n one {# élément}\n other {# éléments}\n}"
},
"loading_25990131": {
"message": "En cours de chargement..."
},
"loading_past_items_ca499e75": {
"message": "Chargement des éléments passés"
},
"loading_planner_items_947a813d": {
"message": "Chargement des éléments du planificateur"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Il semble quil n''y ait rien ici"
},
"missing_1a256b3b": {
"message": "Manquant"
},
"missing_items_for_5ef30cea": {
"message": "Éléments manquants pour "
},
"new_activity_8b84847d": {
"message": "Nouvelle activité"
},
"new_activity_for_ac84f6e6": {
"message": "Nouvelle activité pour "
},
"next_month_749b1778": {
"message": "Mois prochain"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Aucune date d''échéance assignée"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Aucune « activité à faire » identifiée pour aujourdhui."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Rien de nouveau à prendre en charge."
},
"optional_add_course_ef0d70fc": {
"message": "Facultatif : Ajouter un cours"
},
"points_bceb5005": {
"message": "points"
},
"points_points_63e59cce": {
"message": "{ points } points"
},
"previous_month_bb1e3c84": {
"message": "Mois précédent"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Réponses"
},
"save_11a80ec3": {
"message": "Enregistrer"
},
"submitted_318fad53": {
"message": "Soumis"
},
"task_16b0ef38": {
"message": "Tâche"
},
"title_ee03d132": {
"message": "Titre"
},
"title_is_required_6ddcab69": {
"message": "le titre est obligatoire"
},
"to_do_1d554f36": {
"message": "À faire"
},
"today_76e10f9c": {
"message": "Aujourd''hui"
},
"today_at_date_8ac30d6": {
"message": "Aujourd''hui à { date }"
},
"tomorrow_9a6c9a00": {
"message": "Demain"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Demain à { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Impossible de marquer comme Terminé."
},
"yesterday_at_date_1aa6d18e": {
"message": "Hier à { date }"
},
"yesterday_c6bd6abf": {
"message": "Hier"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Vous êtes revenu à votre toute première tâche À faire!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Ajouter à faire"
},
"all_items_loaded_aa256183": {
"message": "Tous les éléments ont été chargés"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Êtes-vous sûr de vouloir supprimer cet élément de planificateur ?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } terminé"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } terminé"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Début de votre historique des choses à faire"
},
"canvas_planner_98ed106": {
"message": "Agenda Canvas"
},
"close_d634289d": {
"message": "Fermer"
},
"close_opportunities_popover_f4e50551": {
"message": "Fermer l''infobulle des opportunités"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# occasions}\n one {# occasion}\n other {# occasions}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Afficher # lélément terminé}\n other { Afficher # les éléments terminés}\n}"
},
"course_8a63b4a3": {
"message": "Cours"
},
"course_to_do_bcbbab54": {
"message": "{ course } À FAIRE"
},
"date_at_time_dbdb1b99": {
"message": "{ date } à { time }"
},
"date_ee500367": {
"message": "Date"
},
"date_is_required_8660ec22": {
"message": "Une date est requise"
},
"delete_a6efa79d": {
"message": "Supprimer"
},
"details_98a31b68": {
"message": "Détails"
},
"dismiss_opportunityname_5995176f": {
"message": "Rejeter { opportunityName }"
},
"due_5d7267be": {
"message": "À rendre le :"
},
"due_date_8ce289b6": {
"message": "ÉCHÉANCE : { date }"
},
"edit_title_72e5a21e": {
"message": "Modifier { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Erreur lors du chargement des éléments supplémentaires"
},
"error_loading_past_items_2881dbb1": {
"message": "Erreur lors du chargement des éléments passés"
},
"excused_cf8792eb": {
"message": "Excusé"
},
"failed_to_delete_to_do_64edff49": {
"message": "Échec de la suppression de la chose à faire"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Échec de la récupération de nouvelles activités"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Échec du chargement des opportunités"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Échec de l''enregistrement de la chose à faire"
},
"feedback_6dcc1991": {
"message": "Commentaire"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Aller à la vue Cartes du tableau de bord"
},
"graded_25cd3fcd": {
"message": "Noté"
},
"late_2be42b88": {
"message": "En retard"
},
"load_more_a36f9cf9": {
"message": "En charger plus"
},
"load_prior_dates_f2b0f6f0": {
"message": "Charger les dates précédentes"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Chargé { count, plural,\n =0{# éléments}\n one {# élément}\n other {# éléments}\n}"
},
"loading_25990131": {
"message": "Chargement..."
},
"loading_past_items_ca499e75": {
"message": "Charger les éléments passés"
},
"loading_planner_items_947a813d": {
"message": "Téléchargement des éléments de l'agenda"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Apparemment, il n''y a rien ici."
},
"missing_1a256b3b": {
"message": "Manquant"
},
"missing_items_for_5ef30cea": {
"message": "Éléments manquants pour "
},
"new_activity_8b84847d": {
"message": "Nouvelle activité"
},
"new_activity_for_ac84f6e6": {
"message": "Nouvelle activité pour "
},
"next_month_749b1778": {
"message": "Mois suivant"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Pas de dates limites de rendu"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Pas de choses à faire pour aujourd''hui."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Aucune nouvelle info nécessitant votre attention"
},
"optional_add_course_ef0d70fc": {
"message": "Facultatif : Ajouter un cours"
},
"points_bceb5005": {
"message": "points"
},
"points_points_63e59cce": {
"message": "{ points } points"
},
"previous_month_bb1e3c84": {
"message": "Mois précédent"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Réponses"
},
"save_11a80ec3": {
"message": "Enregistrer"
},
"submitted_318fad53": {
"message": "Soumis"
},
"task_16b0ef38": {
"message": "Tâche"
},
"title_ee03d132": {
"message": "Titre"
},
"title_is_required_6ddcab69": {
"message": "titre est obligatoire"
},
"to_do_1d554f36": {
"message": "À faire"
},
"today_76e10f9c": {
"message": "Aujourd''hui"
},
"today_at_date_8ac30d6": {
"message": "Aujourd''hui à { date }"
},
"tomorrow_9a6c9a00": {
"message": "Demain"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Demain à { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Impossible de marquer comme terminé."
},
"yesterday_at_date_1aa6d18e": {
"message": "Hier à { date }"
},
"yesterday_c6bd6abf": {
"message": "Hier"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Vous êtes revenu à votre toute première chose à faire."
}
}

View File

@ -0,0 +1,200 @@
{
"add_to_do_7def3c37": {
"message": "הוספת מטלה לביצוע"
},
"all_items_loaded_aa256183": {
"message": "כל הפריטים נטענו"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "האם אתם בטוחים שברצונכם למחוק את פריט המתכנן הזה?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } הושלמה"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } הושלמה"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "התחלת ההיסטוריה של המטלות לביצוע שלך"
},
"canvas_planner_98ed106": {
"message": "מתכנן קנבס"
},
"close_d634289d": {
"message": "סגירה"
},
"close_opportunities_popover_f4e50551": {
"message": "חלון סגירת הזדמנויות"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# הזדמנויות}\n one {הזדמנות #}\n other {# הזדמנויות}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {הצגת פריט # שהושלם}\n other {הצגת # פריטים שהושלמו}\n}"
},
"course_8a63b4a3": {
"message": "קורס"
},
"course_to_do_bcbbab54": {
"message": "{ course } לביצוע"
},
"date_at_time_dbdb1b99": {
"message": "בתאריך { date } בשעה { time }"
},
"date_ee500367": {
"message": "תאריך"
},
"delete_a6efa79d": {
"message": "ביטול"
},
"details_98a31b68": {
"message": "פרטים"
},
"dismiss_opportunityname_5995176f": {
"message": "ויתור על { opportunityName }"
},
"due_5d7267be": {
"message": "תאריך יעד:"
},
"due_date_8ce289b6": {
"message": "תאריך יעד: { date }"
},
"edit_title_72e5a21e": {
"message": "עריכת { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "שגיאה בטעינת פריטים נוספים"
},
"error_loading_past_items_2881dbb1": {
"message": "שגיאה בטעינת פריטים קודמים"
},
"excused_cf8792eb": {
"message": "נסלח"
},
"failed_to_delete_to_do_64edff49": {
"message": "ביטול המטלה לביצוע נכשל"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "כשלון בקבלת פעילות חדשה"
},
"failed_to_load_opportunities_52ab6404": {
"message": "טעינת הזדמנויות נכשלה"
},
"failed_to_save_to_do_ddc7503b": {
"message": "שמירת המטלה לביצוע נכשלה"
},
"feedback_6dcc1991": {
"message": "משוב"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "מעבר לתצוגת כרטיס לוח המחוונים"
},
"graded_25cd3fcd": {
"message": "ניתן ציון"
},
"late_2be42b88": {
"message": "איחור"
},
"load_more_a36f9cf9": {
"message": "טענו עוד"
},
"load_prior_dates_f2b0f6f0": {
"message": "טעינת מועדים קודמים"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "נטענו { count, plural,\n =0 {# פריטים}\n one {פריט #}\n other {# פריטים}\n}"
},
"loading_25990131": {
"message": "בטעינה... "
},
"loading_past_items_ca499e75": {
"message": "בטעינת פריטים קודמים"
},
"loading_planner_items_947a813d": {
"message": "טעינת פריטי המתכנן"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "נראה שאין פה כלום"
},
"missing_1a256b3b": {
"message": "חסר"
},
"missing_items_for_5ef30cea": {
"message": "חסרים פריטים עבור"
},
"new_activity_8b84847d": {
"message": "פעילות חדשה"
},
"new_activity_for_ac84f6e6": {
"message": "פעילות חדשה עבור"
},
"next_month_749b1778": {
"message": "החודש הבא"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "לא הוגדרו תאריכי יעד"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "עדיין אין ״מטלות לביצוע״ לתאריך זה."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "אין כל דבר חדש לציין"
},
"optional_add_course_ef0d70fc": {
"message": "אופציונלי: הוספת קורס"
},
"points_bceb5005": {
"message": "נקודות"
},
"points_points_63e59cce": {
"message": "{ points } נקודות"
},
"previous_month_bb1e3c84": {
"message": "החודש הקודם"
},
"pts_699bd9aa": {
"message": "נקודות"
},
"replies_4a8577c8": {
"message": "תגובות"
},
"save_11a80ec3": {
"message": "שמירה "
},
"submitted_318fad53": {
"message": "הוגשה"
},
"task_16b0ef38": {
"message": "מטלה"
},
"title_ee03d132": {
"message": "כותרת"
},
"title_is_required_6ddcab69": {
"message": "דרושה כותרת"
},
"to_do_1d554f36": {
"message": "לביצוע"
},
"today_76e10f9c": {
"message": "היום"
},
"today_at_date_8ac30d6": {
"message": "היום, בתאריך { date }"
},
"tomorrow_9a6c9a00": {
"message": "מחר"
},
"tomorrow_at_date_b53f2cf1": {
"message": "מחר, בתאריך { date }"
},
"yesterday_at_date_1aa6d18e": {
"message": "אתמול, בתאריך { date }"
},
"yesterday_c6bd6abf": {
"message": "אתמול"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "גללת אחורה למטלה לביצוע הראשונה שלך!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Ajoute Lis Tach"
},
"all_items_loaded_aa256183": {
"message": "Tout eleman yo chaje"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Èske vrèman ou vle elemine eleman planifikatè sa a?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } konplete"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } enkonplè"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Kòmansman Istwa Tach ou yo"
},
"canvas_planner_98ed106": {
"message": "Planifikatè Canvas"
},
"close_d634289d": {
"message": "Fèmen"
},
"close_opportunities_popover_f4e50551": {
"message": "Fèmen ti fenèt opòtinite a"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# opòtinite}\n one {# opòtinite}\n other {# opòtinite}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Afiche # eleman konplete}\n other {Afiche # eleman konplete}\n}"
},
"course_8a63b4a3": {
"message": "Kou"
},
"course_to_do_bcbbab54": {
"message": "{ course } POU FÈ"
},
"date_at_time_dbdb1b99": {
"message": "{ date } nan { time }"
},
"date_ee500367": {
"message": "Dat"
},
"date_is_required_8660ec22": {
"message": "Dat obligatwa"
},
"delete_a6efa79d": {
"message": "Efase"
},
"details_98a31b68": {
"message": "Detay"
},
"dismiss_opportunityname_5995176f": {
"message": "Rejte { opportunityName }"
},
"due_5d7267be": {
"message": "Delè:"
},
"due_date_8ce289b6": {
"message": "DELÈ: { date }"
},
"edit_title_72e5a21e": {
"message": "Modifye { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Erè chajman plis eleman"
},
"error_loading_past_items_2881dbb1": {
"message": "Erè chajman eleman pase yo"
},
"excused_cf8792eb": {
"message": "Egzante"
},
"failed_to_delete_to_do_64edff49": {
"message": "Echèk eliminasyon tach"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Echwe nan chèche nouvo aktivite"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Echèk chajman opòtinite"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Echèk anrejistreman tach"
},
"feedback_6dcc1991": {
"message": "Kòmantè"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Ale nan Afichaj Tablo"
},
"graded_25cd3fcd": {
"message": "Klase"
},
"late_2be42b88": {
"message": "An reta"
},
"load_more_a36f9cf9": {
"message": "Chaje plis"
},
"load_prior_dates_f2b0f6f0": {
"message": "Chaje dat avan"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Chaje { count, plural,\n =0{# eleman}\n one {# eleman}\n other {# eleman}\n}"
},
"loading_25990131": {
"message": "Chajman..."
},
"loading_past_items_ca499e75": {
"message": "Chaje eleman pase"
},
"loading_planner_items_947a813d": {
"message": "Chajman Eleman Planifikatè"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Ta sanble pa gen anyen la a"
},
"missing_1a256b3b": {
"message": "Manke"
},
"missing_items_for_5ef30cea": {
"message": "Eleman manke pou "
},
"new_activity_8b84847d": {
"message": "Nouvo Aktivite"
},
"new_activity_for_ac84f6e6": {
"message": "Nouvo aktivite pou "
},
"next_month_749b1778": {
"message": "Mwa Pwochen"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Yo Pa Bay Delè"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Poko gen \"Okenn Tach\" pou jou sa a "
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Pa gen anyen nouvo ki mande atansyon."
},
"optional_add_course_ef0d70fc": {
"message": "Opsyonèl: Ajoute Kou"
},
"points_bceb5005": {
"message": "pwen"
},
"points_points_63e59cce": {
"message": "{ points } pwen"
},
"previous_month_bb1e3c84": {
"message": "Mwa Pase"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Repons"
},
"save_11a80ec3": {
"message": "Anrejistre"
},
"submitted_318fad53": {
"message": "Soumèt"
},
"task_16b0ef38": {
"message": "Tach"
},
"title_ee03d132": {
"message": "Tit"
},
"title_is_required_6ddcab69": {
"message": "tit obligatwa"
},
"to_do_1d554f36": {
"message": "Pou Fè"
},
"today_76e10f9c": {
"message": "Jodi a"
},
"today_at_date_8ac30d6": {
"message": "Jodi a a { date }"
},
"tomorrow_9a6c9a00": {
"message": "Demen"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Demen a { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Enposib pou make kòm konplè."
},
"yesterday_at_date_1aa6d18e": {
"message": "Yè a { date }"
},
"yesterday_c6bd6abf": {
"message": "Yè"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Ou retounan nan premye Tach ou yo!"
}
}

View File

@ -0,0 +1,71 @@
{
"close_d634289d": {
"message": "Bezár"
},
"course_8a63b4a3": {
"message": "Kurzus"
},
"date_ee500367": {
"message": "Dátum"
},
"delete_a6efa79d": {
"message": "Törlés"
},
"details_98a31b68": {
"message": "Részletek"
},
"due_5d7267be": {
"message": "Határidő: "
},
"excused_cf8792eb": {
"message": "Felmentve"
},
"graded_25cd3fcd": {
"message": "Értékelve"
},
"late_2be42b88": {
"message": "Késve"
},
"load_more_a36f9cf9": {
"message": "Továbbiak betöltése"
},
"loading_25990131": {
"message": "Betöltés..."
},
"missing_1a256b3b": {
"message": "Hiányzik"
},
"next_month_749b1778": {
"message": "Következő hónap"
},
"points_bceb5005": {
"message": "pontszám"
},
"previous_month_bb1e3c84": {
"message": "Előző hónap"
},
"pts_699bd9aa": {
"message": "pont"
},
"save_11a80ec3": {
"message": "Mentés"
},
"submitted_318fad53": {
"message": "Beadva"
},
"title_ee03d132": {
"message": "Cím"
},
"to_do_1d554f36": {
"message": "Teendők"
},
"today_76e10f9c": {
"message": "Ma"
},
"tomorrow_9a6c9a00": {
"message": "Holnap"
},
"yesterday_c6bd6abf": {
"message": "Tegnap"
}
}

View File

@ -0,0 +1,56 @@
{
"close_d634289d": {
"message": "Փակել"
},
"course_8a63b4a3": {
"message": "Դասընթաց"
},
"date_ee500367": {
"message": "Ամսաթիվ"
},
"delete_a6efa79d": {
"message": "Ջնջել"
},
"details_98a31b68": {
"message": "Մանրամասներ"
},
"due_5d7267be": {
"message": "Մինչև"
},
"excused_cf8792eb": {
"message": "Ներված է"
},
"graded_25cd3fcd": {
"message": "Գնահատվող"
},
"load_more_a36f9cf9": {
"message": "Բեռնել լրացուցիչ"
},
"loading_25990131": {
"message": "Բեռնում է..."
},
"pts_699bd9aa": {
"message": "միավորներ"
},
"save_11a80ec3": {
"message": "Պահպանել"
},
"submitted_318fad53": {
"message": "Ներկայացված է "
},
"title_ee03d132": {
"message": "Վերնագիր"
},
"to_do_1d554f36": {
"message": "Անել"
},
"today_76e10f9c": {
"message": "Այսօր"
},
"tomorrow_9a6c9a00": {
"message": "Վաղը"
},
"yesterday_c6bd6abf": {
"message": "Երեկ"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Bæta við Verkefni"
},
"all_items_loaded_aa256183": {
"message": "Öllum atriðum hefur verið hlaðið"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": " Ertu viss um að þú viljir eyða þessu skipulagi?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } er lokið"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } er ekki lokið"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Upphaf verkefnasögu þinnar"
},
"canvas_planner_98ed106": {
"message": "Canvas-skipuleggjari"
},
"close_d634289d": {
"message": "Loka"
},
"close_opportunities_popover_f4e50551": {
"message": "Loka sprettiglugga yfir kostum"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# Kostir}\n one {# kostur}\n other {# kostir}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Sýna # lokið atriði}\n other {Sýna # lokin atriði}\n}"
},
"course_8a63b4a3": {
"message": "Námskeið"
},
"course_to_do_bcbbab54": {
"message": "{ course } VERKEFNI"
},
"date_at_time_dbdb1b99": {
"message": "{ date } á { time }"
},
"date_ee500367": {
"message": "Dagsetning"
},
"date_is_required_8660ec22": {
"message": "Dagsetningar er krafist"
},
"delete_a6efa79d": {
"message": "Eyða"
},
"details_98a31b68": {
"message": "Upplýsingar"
},
"dismiss_opportunityname_5995176f": {
"message": "Hafna { opportunityName }"
},
"due_5d7267be": {
"message": "Skiladagur:"
},
"due_date_8ce289b6": {
"message": "SKILADAGSETNING { date }"
},
"edit_title_72e5a21e": {
"message": "Breyta { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Villa kom upp við að hlaða fleiri atriðum"
},
"error_loading_past_items_2881dbb1": {
"message": "Villa kom upp við að hlaða eldri atriðum"
},
"excused_cf8792eb": {
"message": "Ekki með"
},
"failed_to_delete_to_do_64edff49": {
"message": "Mistókst að eyða Verkefni"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Mistókst að sækja nýja virkni"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Mistókst að hlaða kostum"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Mistókst að vista Verkefni"
},
"feedback_6dcc1991": {
"message": "Endurgjöf"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Fara í sýn skjáborðskorts"
},
"graded_25cd3fcd": {
"message": "Metið"
},
"late_2be42b88": {
"message": "Seint"
},
"load_more_a36f9cf9": {
"message": "Hlaða meira"
},
"load_prior_dates_f2b0f6f0": {
"message": "Hlaða fyrri dagsetningum"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Hlaðin { count, plural,\n =0 {# atriði}\n one {# atriði}\n other {# atriði}\n}"
},
"loading_25990131": {
"message": "Hleður..."
},
"loading_past_items_ca499e75": {
"message": "Hlaða fyrri atriðum"
},
"loading_planner_items_947a813d": {
"message": "Hleður skipulögðum atriðum"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Lítur út fyrir að vera autt"
},
"missing_1a256b3b": {
"message": "Vantar"
},
"missing_items_for_5ef30cea": {
"message": "Atriði sem vantar fyrir "
},
"new_activity_8b84847d": {
"message": "Ný virkni"
},
"new_activity_for_ac84f6e6": {
"message": "Ný virkni fyrir "
},
"next_month_749b1778": {
"message": "Næsti mánuður"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Engum skiladagsetningum úthlutað"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Engin „verkefni“ eru enn fyrir þennan dag,"
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Ekkert nýtt sem krefst athygli."
},
"optional_add_course_ef0d70fc": {
"message": "Valkvætt: Bæta við námskeiði"
},
"points_bceb5005": {
"message": "punktar"
},
"points_points_63e59cce": {
"message": "{ points } punktar"
},
"previous_month_bb1e3c84": {
"message": "Fyrri mánuður"
},
"pts_699bd9aa": {
"message": "punktar"
},
"replies_4a8577c8": {
"message": "Svör"
},
"save_11a80ec3": {
"message": "Vista"
},
"submitted_318fad53": {
"message": "Skilað"
},
"task_16b0ef38": {
"message": "Verki"
},
"title_ee03d132": {
"message": "Titill"
},
"title_is_required_6ddcab69": {
"message": "titill er áskilinn"
},
"to_do_1d554f36": {
"message": "Verkefni"
},
"today_76e10f9c": {
"message": "Í dag"
},
"today_at_date_8ac30d6": {
"message": "Í dag kl. { date }"
},
"tomorrow_9a6c9a00": {
"message": "Á morgun"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Á morgun kl. { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Ekki tókst að merkja þetta sem fullklárað."
},
"yesterday_at_date_1aa6d18e": {
"message": "Í gær kl. { date }"
},
"yesterday_c6bd6abf": {
"message": "Í gær"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Þú hefur skrunað aftur í fyrsta verkefnið þitt!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Aggiungi elenco attività"
},
"all_items_loaded_aa256183": {
"message": "Tutti gli elementi caricati"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Vuoi eliminare questo elemento dell'agenda?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } completata"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } non completata"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Inizio della cronologia dell'elenco attività"
},
"canvas_planner_98ed106": {
"message": "Canvas Planner"
},
"close_d634289d": {
"message": "Chiudi"
},
"close_opportunities_popover_f4e50551": {
"message": "Chiudi popover opportunità"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# opportunità}\n one {# opportunità}\n other {# opportunità}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Mostra # elemento completato}\n other {Mostra # elementi completati}\n}"
},
"course_8a63b4a3": {
"message": "Corso"
},
"course_to_do_bcbbab54": {
"message": "{ course } ELENCO ATTIVITÀ"
},
"date_at_time_dbdb1b99": {
"message": "{ date } alle { time }"
},
"date_ee500367": {
"message": "Data"
},
"date_is_required_8660ec22": {
"message": "Data obbligatoria"
},
"delete_a6efa79d": {
"message": "Elimina"
},
"details_98a31b68": {
"message": "Dettagli"
},
"dismiss_opportunityname_5995176f": {
"message": "Ignora { opportunityName }"
},
"due_5d7267be": {
"message": "Scadenza:"
},
"due_date_8ce289b6": {
"message": "SCADENZA: { date }"
},
"edit_title_72e5a21e": {
"message": "Modifica { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Errore durante il caricamento di più elementi"
},
"error_loading_past_items_2881dbb1": {
"message": "Errore durante il caricamento degli elementi precedenti"
},
"excused_cf8792eb": {
"message": "Giustificato"
},
"failed_to_delete_to_do_64edff49": {
"message": "Impossibile eliminare attività"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Impossibile trovare la nuova attività"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Impossibile caricare le opportunità"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Impossibile salvare attività"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Vai alla visualizzazione schede dashboard"
},
"graded_25cd3fcd": {
"message": "Valutato"
},
"late_2be42b88": {
"message": "Tardi"
},
"load_more_a36f9cf9": {
"message": "Carica altro"
},
"load_prior_dates_f2b0f6f0": {
"message": "Carica date precedenti"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Caricati { count, plural,\n =0{# elementi}\n one {# elemento}\n other {# elementi}\n}"
},
"loading_25990131": {
"message": "Caricamento in corso..."
},
"loading_past_items_ca499e75": {
"message": "Caricamento elementi precedenti"
},
"loading_planner_items_947a813d": {
"message": "Caricamento voci agenda"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Sembra non ci sia nulla qui"
},
"missing_1a256b3b": {
"message": "Mancante"
},
"missing_items_for_5ef30cea": {
"message": "Elementi mancanti per "
},
"new_activity_8b84847d": {
"message": "Nuova attività"
},
"new_activity_for_ac84f6e6": {
"message": "Nuova attività per "
},
"next_month_749b1778": {
"message": "Mese successivo"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Nessuna data di scadenza assegnata"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Ancora nessun elenco attività per oggi."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nulla di nuovo richiede attenzione."
},
"optional_add_course_ef0d70fc": {
"message": "Facoltativo: Aggiungi corso"
},
"points_bceb5005": {
"message": "punti"
},
"points_points_63e59cce": {
"message": "{ points } punti"
},
"previous_month_bb1e3c84": {
"message": "Mese precedente"
},
"pts_699bd9aa": {
"message": "pt"
},
"replies_4a8577c8": {
"message": "Risposte"
},
"save_11a80ec3": {
"message": "Salva"
},
"submitted_318fad53": {
"message": "Inviato"
},
"task_16b0ef38": {
"message": "Attività"
},
"title_ee03d132": {
"message": "Titolo"
},
"title_is_required_6ddcab69": {
"message": "titolo obbligatorio"
},
"to_do_1d554f36": {
"message": "Elenco attività"
},
"today_76e10f9c": {
"message": "Oggi"
},
"today_at_date_8ac30d6": {
"message": "Oggi alle { date }"
},
"tomorrow_9a6c9a00": {
"message": "Domani"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Domani alle { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Impossibile contrassegnare come completato."
},
"yesterday_at_date_1aa6d18e": {
"message": "Ieri alle { date }"
},
"yesterday_c6bd6abf": {
"message": "Ieri"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Hai raggiunto l'inizio dell'elenco attività."
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "タスクに追加"
},
"all_items_loaded_aa256183": {
"message": "すべてのアイテムがロードされました"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "この計画項目をほんとうに削除しますか?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } 完了しています"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } 完了しています"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "あなたのタスク履歴の始まり"
},
"canvas_planner_98ed106": {
"message": "Canvas プランナー"
},
"close_d634289d": {
"message": "閉じる"
},
"close_opportunities_popover_f4e50551": {
"message": "機会のクローズアップを閉じる"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# 機会}\n one {# 機会}\n other {# 機会}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {表示する# (完了したアイテムを}\n other {表示する # (完了したアイテムを)}\n}"
},
"course_8a63b4a3": {
"message": "コース"
},
"course_to_do_bcbbab54": {
"message": "{ course } タスク"
},
"date_at_time_dbdb1b99": {
"message": "{ date } おいて{ time }"
},
"date_ee500367": {
"message": "日付"
},
"date_is_required_8660ec22": {
"message": "日付は必須です"
},
"delete_a6efa79d": {
"message": "削除"
},
"details_98a31b68": {
"message": "詳細"
},
"dismiss_opportunityname_5995176f": {
"message": "却下する{ opportunityName }"
},
"due_5d7267be": {
"message": "期限:"
},
"due_date_8ce289b6": {
"message": "期限:{ date }"
},
"edit_title_72e5a21e": {
"message": "編集{ title }"
},
"error_loading_more_items_3f109d9f": {
"message": "さらなるアイテムの読み込み中にエラーが発生しました"
},
"error_loading_past_items_2881dbb1": {
"message": "過去のアイテムの読み込み中にエラーが発生しました"
},
"excused_cf8792eb": {
"message": "免除"
},
"failed_to_delete_to_do_64edff49": {
"message": "タスクの削除に失敗しました"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "新規アクティビティ取得に失敗しました"
},
"failed_to_load_opportunities_52ab6404": {
"message": "機会が読み込めませんでした"
},
"failed_to_save_to_do_ddc7503b": {
"message": "タスクの保存に失敗しました"
},
"feedback_6dcc1991": {
"message": "フィードバック"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "ダッシュボードカードビューに移動"
},
"graded_25cd3fcd": {
"message": "採点済み"
},
"late_2be42b88": {
"message": "遅れている"
},
"load_more_a36f9cf9": {
"message": "もっと読み込む"
},
"load_prior_dates_f2b0f6f0": {
"message": "以前のデータを読み込む"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "{ count, plural,\n =0{# アイテム}\n one {# アイテム}\n other {# アイテムを読み込みました}\n}"
},
"loading_25990131": {
"message": "読み込み中・・・"
},
"loading_past_items_ca499e75": {
"message": "過去のアイテムを読み込む"
},
"loading_planner_items_947a813d": {
"message": "プランナーアイテムをもっと読み込む"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "ここに何もないようです"
},
"missing_1a256b3b": {
"message": "欠如"
},
"missing_items_for_5ef30cea": {
"message": "~に不足しているアイテム"
},
"new_activity_8b84847d": {
"message": "新規アクティビティ"
},
"new_activity_for_ac84f6e6": {
"message": "~の新規アクティビティ"
},
"next_month_749b1778": {
"message": "翌月"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "期日指定なし"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "この日はまだタスクがありません。"
},
"nothing_new_needs_attention_3ac548d4": {
"message": "注意を必要する新しいものは何もない。"
},
"optional_add_course_ef0d70fc": {
"message": "オプション:コースの追加"
},
"points_bceb5005": {
"message": "ポイント"
},
"points_points_63e59cce": {
"message": "{ points } ポイント"
},
"previous_month_bb1e3c84": {
"message": "前月"
},
"pts_699bd9aa": {
"message": "ポイント"
},
"replies_4a8577c8": {
"message": "返信"
},
"save_11a80ec3": {
"message": "保存"
},
"submitted_318fad53": {
"message": "タスクの成績を送信"
},
"task_16b0ef38": {
"message": "タスク"
},
"title_ee03d132": {
"message": "タイトル"
},
"title_is_required_6ddcab69": {
"message": "タイトルは必須です"
},
"to_do_1d554f36": {
"message": "タスク"
},
"today_76e10f9c": {
"message": "今日"
},
"today_at_date_8ac30d6": {
"message": "今日 にて{ date }"
},
"tomorrow_9a6c9a00": {
"message": "明日"
},
"tomorrow_at_date_b53f2cf1": {
"message": "明日  にて{ date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "終了とマークできません。"
},
"yesterday_at_date_1aa6d18e": {
"message": "昨日  にて{ date }"
},
"yesterday_c6bd6abf": {
"message": "昨日"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "あなたの最初のタスクにスクロールしました!"
}
}

View File

@ -0,0 +1,56 @@
{
"close_d634289d": {
"message": "닫기"
},
"course_8a63b4a3": {
"message": "과목"
},
"date_ee500367": {
"message": "날짜"
},
"delete_a6efa79d": {
"message": "삭제"
},
"details_98a31b68": {
"message": "세부 정보"
},
"due_5d7267be": {
"message": "기한:"
},
"graded_25cd3fcd": {
"message": "평가됨"
},
"load_more_a36f9cf9": {
"message": "더 로드"
},
"loading_25990131": {
"message": "로드하는 중..."
},
"points_bceb5005": {
"message": "점수"
},
"pts_699bd9aa": {
"message": "점"
},
"save_11a80ec3": {
"message": "저장"
},
"submitted_318fad53": {
"message": "제출됨"
},
"title_ee03d132": {
"message": "제목"
},
"to_do_1d554f36": {
"message": "할 일"
},
"today_76e10f9c": {
"message": "오늘"
},
"tomorrow_9a6c9a00": {
"message": "내일"
},
"yesterday_c6bd6abf": {
"message": "어제"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Tāpiri hei mahi"
},
"all_items_loaded_aa256183": {
"message": "Kua utaina ngā tūemi katoa"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "E pono ana koe kei te hiahia koe kia mukua tenei mahere tūemi?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } Kua oti"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } Kaore i te oti"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Te timatatanga o tō hītori Hei-Mahi"
},
"canvas_planner_98ed106": {
"message": "Canvas Mahere"
},
"close_d634289d": {
"message": "Katia"
},
"close_opportunities_popover_f4e50551": {
"message": "Katia whai wāhitanga pakū ki runga"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# Ngā whaiwāhitanga}\n one {# whaiwāhitanga}\n other {# ngā whaiwāhitanga}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Whakaatu # tūemi kua oti}\n other {Whakaatu # ngā tūemi kua oti}\n}"
},
"course_8a63b4a3": {
"message": "akorangas"
},
"course_to_do_bcbbab54": {
"message": "{ course } Hei mahi"
},
"date_at_time_dbdb1b99": {
"message": "{ date } ī { time }"
},
"date_ee500367": {
"message": "Rā"
},
"date_is_required_8660ec22": {
"message": "He rā e hiahiatia ana."
},
"delete_a6efa79d": {
"message": "Muku"
},
"details_98a31b68": {
"message": "taipitopito"
},
"dismiss_opportunityname_5995176f": {
"message": "Pana { opportunityName }"
},
"due_5d7267be": {
"message": "E tika ana:"
},
"due_date_8ce289b6": {
"message": "E TIKA ANA: { date }"
},
"edit_title_72e5a21e": {
"message": "Whakatika { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "He hapa i puta i te wā e uta ana nui atu tuemi"
},
"error_loading_past_items_2881dbb1": {
"message": "He hapa i puta i te wā e uta ana ngā tuemi o mua"
},
"excused_cf8792eb": {
"message": "Tukua"
},
"failed_to_delete_to_do_64edff49": {
"message": "I rāhua ki te muku hei mahi"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "I rāhua ki te tiki mahi hou"
},
"failed_to_load_opportunities_52ab6404": {
"message": "I rāhua i te wā uta "
},
"failed_to_save_to_do_ddc7503b": {
"message": "I rāhua ki te whakaora hei mahi"
},
"feedback_6dcc1991": {
"message": "Urupare"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Haere ki te Papatohu Kāri Tirohia"
},
"graded_25cd3fcd": {
"message": "mākahia"
},
"late_2be42b88": {
"message": "Tūreiti"
},
"load_more_a36f9cf9": {
"message": "Uta nui atu"
},
"load_prior_dates_f2b0f6f0": {
"message": "Uta ngā rā o mua"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Utaina { count, plural,\n =0{# ngā tuemi }\n one {# tūemi }\n other {# ngā tuemi}\n}"
},
"loading_25990131": {
"message": "E Uta ana...."
},
"loading_past_items_ca499e75": {
"message": "E uta ana ngā tūemi o muri"
},
"loading_planner_items_947a813d": {
"message": "Uta ana mahere tūemi"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Titiro rite kaore tētahi i kōnei"
},
"missing_1a256b3b": {
"message": "Ngaro"
},
"missing_items_for_5ef30cea": {
"message": "Ngā mea ngaro mō "
},
"new_activity_8b84847d": {
"message": "Mahi hou"
},
"new_activity_for_ac84f6e6": {
"message": "He mahi hou mō "
},
"next_month_749b1778": {
"message": "Tērā marama"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Kaore he rā tika i whakaritea"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Kaore he “Hei Mahi” mo tēnei rā anō."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Kaore he mea hou hei aro atu"
},
"optional_add_course_ef0d70fc": {
"message": "Kāre Herea: Tāpiri akoranga"
},
"points_bceb5005": {
"message": "Ngā koinga"
},
"points_points_63e59cce": {
"message": "{ points } Ngā koinga"
},
"previous_month_bb1e3c84": {
"message": "Te marama o muri nei"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Ngā whakautu"
},
"save_11a80ec3": {
"message": "Tiaki"
},
"submitted_318fad53": {
"message": "Kua tukuna"
},
"task_16b0ef38": {
"message": "Mahi"
},
"title_ee03d132": {
"message": "taitara"
},
"title_is_required_6ddcab69": {
"message": "Te taitara e hiahiatia ana"
},
"to_do_1d554f36": {
"message": "hei mahi"
},
"today_76e10f9c": {
"message": "Tēnei rā"
},
"today_at_date_8ac30d6": {
"message": "I tēnei rā ī { date }"
},
"tomorrow_9a6c9a00": {
"message": "apopo"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Apopo ī { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Kaore e taea te tohu kua oti."
},
"yesterday_at_date_1aa6d18e": {
"message": "Inānahi rā { date }"
},
"yesterday_c6bd6abf": {
"message": "Inanahi"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Kua hoki koe ki to Hei - Mahi tino tuatahi!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "To-do lijst toevoegen"
},
"all_items_loaded_aa256183": {
"message": "Alle items geladen"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Weet je zeker dat je dit planneritem wilt verwijderen?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } is voltooid"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } is onvolledig"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Begin van je To-do-historie"
},
"canvas_planner_98ed106": {
"message": "Canvas planner"
},
"close_d634289d": {
"message": "Sluiten"
},
"close_opportunities_popover_f4e50551": {
"message": "Sluit de pop-up voor mogelijkheden"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# mogelijkheden}\n one {# mogelijkheid}\n other {# mogelijkheden}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Voltooid item # tonen}\n other {Voltooide items # tonen}\n}"
},
"course_8a63b4a3": {
"message": "Cursus"
},
"course_to_do_bcbbab54": {
"message": "{ course } TO-DO"
},
"date_at_time_dbdb1b99": {
"message": "{ date } om { time }"
},
"date_ee500367": {
"message": "Datum"
},
"date_is_required_8660ec22": {
"message": "Datum is verplicht"
},
"delete_a6efa79d": {
"message": "Verwijderen"
},
"details_98a31b68": {
"message": "Details"
},
"dismiss_opportunityname_5995176f": {
"message": "Afwijzen { opportunityName }"
},
"due_5d7267be": {
"message": "Inleverdatum:"
},
"due_date_8ce289b6": {
"message": "VERVALDATUM: { date }"
},
"edit_title_72e5a21e": {
"message": "Bewerken { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Fout bij het laden van meer items"
},
"error_loading_past_items_2881dbb1": {
"message": "Fout bij het laden van eerdere items"
},
"excused_cf8792eb": {
"message": "Vrijgesteld"
},
"failed_to_delete_to_do_64edff49": {
"message": "Kan To Do-lijst niet sluiten"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Kan geen nieuwe activiteit ophalen"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Kan mogelijkheden niet laden"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Kan To Do-lijst niet opslaan"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Ga naar de weergave Dashboardkaarten"
},
"graded_25cd3fcd": {
"message": "Beoordeeld"
},
"late_2be42b88": {
"message": "Laat"
},
"load_more_a36f9cf9": {
"message": "Meer laden"
},
"load_prior_dates_f2b0f6f0": {
"message": "Eerdere datums laden"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "{ count, plural,\n =0{# items}\n one {# item}\n other {# items geladen}\n}"
},
"loading_25990131": {
"message": "Bezig met laden..."
},
"loading_past_items_ca499e75": {
"message": "Eerdere items laden"
},
"loading_planner_items_947a813d": {
"message": "Planner items laden"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Het lijkt erop dat er hier niets is"
},
"missing_1a256b3b": {
"message": "Ontbrekend"
},
"missing_items_for_5ef30cea": {
"message": "Ontbrekende items voor "
},
"new_activity_8b84847d": {
"message": "Nieuwe activiteit"
},
"new_activity_for_ac84f6e6": {
"message": "Nieuwe activiteit voor "
},
"next_month_749b1778": {
"message": "Volgende maand"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Geen inleverdatums toegewezen"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Nog niets in de To-Do-lijst voor vandaag."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Er is niets nieuws waarnaar gekeken moet worden."
},
"optional_add_course_ef0d70fc": {
"message": "Optioneel: Cursus toevoegen"
},
"points_bceb5005": {
"message": "punten"
},
"points_points_63e59cce": {
"message": "{ points } punten"
},
"previous_month_bb1e3c84": {
"message": "Vorige maand"
},
"pts_699bd9aa": {
"message": "punten"
},
"replies_4a8577c8": {
"message": "Antwoorden"
},
"save_11a80ec3": {
"message": "Opslaan"
},
"submitted_318fad53": {
"message": "Ingediend"
},
"task_16b0ef38": {
"message": "Taak"
},
"title_ee03d132": {
"message": "Titel"
},
"title_is_required_6ddcab69": {
"message": "titel is verplicht"
},
"to_do_1d554f36": {
"message": "To-do lijst"
},
"today_76e10f9c": {
"message": "Vandaag"
},
"today_at_date_8ac30d6": {
"message": "Vandaag om { date }"
},
"tomorrow_9a6c9a00": {
"message": "Morgen"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Morgen om { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Kan dit niet als voltooid markeren."
},
"yesterday_at_date_1aa6d18e": {
"message": "Gisteren om { date }"
},
"yesterday_c6bd6abf": {
"message": "Gisteren"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Je bent teruggescrold naar je allereerste To-do-item!"
}
}

View File

@ -0,0 +1,68 @@
{
"close_d634289d": {
"message": "Lukk"
},
"course_8a63b4a3": {
"message": "Emne"
},
"date_ee500367": {
"message": "Dato"
},
"delete_a6efa79d": {
"message": "Slett"
},
"details_98a31b68": {
"message": "Detaljar"
},
"due_5d7267be": {
"message": "Frist:"
},
"excused_cf8792eb": {
"message": "Fritak er innvilga"
},
"graded_25cd3fcd": {
"message": "Karaktersett"
},
"late_2be42b88": {
"message": "Sein"
},
"load_more_a36f9cf9": {
"message": "Last opp meir"
},
"loading_25990131": {
"message": "Lastar..."
},
"missing_1a256b3b": {
"message": "Manglar"
},
"points_bceb5005": {
"message": "poeng"
},
"pts_699bd9aa": {
"message": "poeng"
},
"replies_4a8577c8": {
"message": "Svar"
},
"save_11a80ec3": {
"message": "Lagre"
},
"submitted_318fad53": {
"message": "Innlevert"
},
"title_ee03d132": {
"message": "Tittel"
},
"to_do_1d554f36": {
"message": "Å gjere"
},
"today_76e10f9c": {
"message": "I dag"
},
"tomorrow_9a6c9a00": {
"message": "I morgon"
},
"yesterday_c6bd6abf": {
"message": "I går"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Legg til gjøremål"
},
"all_items_loaded_aa256183": {
"message": "Alle elementer lastet opp"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Er du sikker på at du vil slette dette elementet fra planleggeren?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } er fullført"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } er ufullstendig"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Begynnelsen av din gjøremålshistorie"
},
"canvas_planner_98ed106": {
"message": "Canvas planlegger"
},
"close_d634289d": {
"message": "Lukk"
},
"close_opportunities_popover_f4e50551": {
"message": "Lukk mulighets-popover"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# muligheter}\n one {# mulighet}\n other {# muligheter}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Vis # fullført element}\n other {Vis # fullførte elementer}\n}"
},
"course_8a63b4a3": {
"message": "Emne"
},
"course_to_do_bcbbab54": {
"message": "{ course } Å GJØRE"
},
"date_at_time_dbdb1b99": {
"message": "{ date } den { time }"
},
"date_ee500367": {
"message": "Dato"
},
"date_is_required_8660ec22": {
"message": "Dato kreves"
},
"delete_a6efa79d": {
"message": "Slett"
},
"details_98a31b68": {
"message": "Detaljer"
},
"dismiss_opportunityname_5995176f": {
"message": "Avvise { opportunityName }"
},
"due_5d7267be": {
"message": "Frist:"
},
"due_date_8ce289b6": {
"message": "FORFALL: { date }"
},
"edit_title_72e5a21e": {
"message": "Rediger { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Feil under lasting av flere elementer"
},
"error_loading_past_items_2881dbb1": {
"message": "Feil under lasting av tidligere elementer"
},
"excused_cf8792eb": {
"message": "Fritatt"
},
"failed_to_delete_to_do_64edff49": {
"message": "Kunne ikke slette gjøremål"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Kunne ikke få ny aktivitet"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Kunne ikke laste muligheter"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Kunne ikke lagre gjøremål"
},
"feedback_6dcc1991": {
"message": "Tilbakemelding"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Gå til kortvisningen av instrumentpanelet"
},
"graded_25cd3fcd": {
"message": "Karaktersatt"
},
"late_2be42b88": {
"message": "Sent"
},
"load_more_a36f9cf9": {
"message": "Last mer"
},
"load_prior_dates_f2b0f6f0": {
"message": "Last inn tidligere datoer"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Lastet { count, plural,\n =0{# elementer}\n one {# element}\n other {# elementer}\n}"
},
"loading_25990131": {
"message": "Laster..."
},
"loading_past_items_ca499e75": {
"message": "Laster tidligere elementer"
},
"loading_planner_items_947a813d": {
"message": "Laster elementplanlegger"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Ser ikke ut til at det er noe her"
},
"missing_1a256b3b": {
"message": "Mangler"
},
"missing_items_for_5ef30cea": {
"message": "Manglende elementer for "
},
"new_activity_8b84847d": {
"message": "Ny aktivitet"
},
"new_activity_for_ac84f6e6": {
"message": "Ny aktivitet for "
},
"next_month_749b1778": {
"message": "Neste måned"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Ingen forfallsdatoer tildelt"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Ingen gjøremål ennå i dag."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Ingenting nytt trenger oppmerksomhet."
},
"optional_add_course_ef0d70fc": {
"message": "Valgfri: Legg til emne"
},
"points_bceb5005": {
"message": "poeng"
},
"points_points_63e59cce": {
"message": "{ points } poeng"
},
"previous_month_bb1e3c84": {
"message": "Forrige måned"
},
"pts_699bd9aa": {
"message": "poeng"
},
"replies_4a8577c8": {
"message": "Svar"
},
"save_11a80ec3": {
"message": "Lagre"
},
"submitted_318fad53": {
"message": "Innlevert"
},
"task_16b0ef38": {
"message": "Oppgave"
},
"title_ee03d132": {
"message": "Tittel"
},
"title_is_required_6ddcab69": {
"message": "tittel er obligatorisk"
},
"to_do_1d554f36": {
"message": "Å gjøre"
},
"today_76e10f9c": {
"message": "I dag"
},
"today_at_date_8ac30d6": {
"message": "I dag klokken { date }"
},
"tomorrow_9a6c9a00": {
"message": "I morgen"
},
"tomorrow_at_date_b53f2cf1": {
"message": "I morgen klokken { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Kan ikke markere som fullført."
},
"yesterday_at_date_1aa6d18e": {
"message": "I går klokken { date }"
},
"yesterday_c6bd6abf": {
"message": "I går"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Du har rullet tilbake til din aller første gjøremålsliste!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Dodaj do listy zadań"
},
"all_items_loaded_aa256183": {
"message": "Wszystkie elementy wczytane"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Czy na pewno chcesz usunąć ten element planera?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } jest ukończony"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } jest nieukończony"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Początek historii listy zadań"
},
"canvas_planner_98ed106": {
"message": "Planer Canvas"
},
"close_d634289d": {
"message": "Zamknij"
},
"close_opportunities_popover_f4e50551": {
"message": "Zamknij okienko z okazjami"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# okazje}\n one {# okazja}\n other {# okazji}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Pokaż # ukończony element}\n other {Pokaż ukończone elementy: #}\n}"
},
"course_8a63b4a3": {
"message": "Kurs"
},
"course_to_do_bcbbab54": {
"message": "{ course } LISTA ZADAŃ"
},
"date_at_time_dbdb1b99": {
"message": "{ date } o { time }"
},
"date_ee500367": {
"message": "Data"
},
"date_is_required_8660ec22": {
"message": "Wymagana jest data."
},
"delete_a6efa79d": {
"message": "Usuń"
},
"details_98a31b68": {
"message": "Szczegóły"
},
"dismiss_opportunityname_5995176f": {
"message": "Odrzuć { opportunityName }"
},
"due_5d7267be": {
"message": "Termin:"
},
"due_date_8ce289b6": {
"message": "Termin: { date }"
},
"edit_title_72e5a21e": {
"message": "Edytuj { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Błąd podczas wczytywania dodatkowych elementów"
},
"error_loading_past_items_2881dbb1": {
"message": "Błąd podczas wczytywania wcześniejszych elementów"
},
"excused_cf8792eb": {
"message": "Usprawiedliwiony"
},
"failed_to_delete_to_do_64edff49": {
"message": "Nie udało się usunąć zadań"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Nie udało się pobrać nowej aktywności"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Nie udało się wczytać okazji"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Nie udało się zapisać do"
},
"feedback_6dcc1991": {
"message": "Informacje zwrotne"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Przejdź do widoku kart panelu"
},
"graded_25cd3fcd": {
"message": "Oceniono"
},
"late_2be42b88": {
"message": "Późno"
},
"load_more_a36f9cf9": {
"message": "Załaduj więcej"
},
"load_prior_dates_f2b0f6f0": {
"message": "Wczytaj wcześniejsze daty"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Wczytano { count, plural,\n =0{# elementów}\n one {# element}\n other {# elementy}\n}"
},
"loading_25990131": {
"message": "Wczytywanie..."
},
"loading_past_items_ca499e75": {
"message": "Wczytywanie wcześniejszych elementów"
},
"loading_planner_items_947a813d": {
"message": "Wczytywanie elementu planera"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Wygląda na to, że nic tu nie ma"
},
"missing_1a256b3b": {
"message": "Brak"
},
"missing_items_for_5ef30cea": {
"message": "Brak elementów dla "
},
"new_activity_8b84847d": {
"message": "Nowa aktywność"
},
"new_activity_for_ac84f6e6": {
"message": "Nowa aktywność dla "
},
"next_month_749b1778": {
"message": "Następny miesiąc"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Brak ustawionych terminów"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Brak listy zadań ten dzień."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nic nie wymaga uwagi."
},
"optional_add_course_ef0d70fc": {
"message": "Opcjonalnie: Dodaj kurs"
},
"points_bceb5005": {
"message": "punkty"
},
"points_points_63e59cce": {
"message": "{ points } punkty"
},
"previous_month_bb1e3c84": {
"message": "Poprzedni miesiąc"
},
"pts_699bd9aa": {
"message": "pkt"
},
"replies_4a8577c8": {
"message": "Odpowiedzi"
},
"save_11a80ec3": {
"message": "Zapisz"
},
"submitted_318fad53": {
"message": "Przesłano"
},
"task_16b0ef38": {
"message": "Zadanie"
},
"title_ee03d132": {
"message": "Tytuł"
},
"title_is_required_6ddcab69": {
"message": "Wymagany jest tytuł"
},
"to_do_1d554f36": {
"message": "Lista zadań"
},
"today_76e10f9c": {
"message": "Dzisiaj"
},
"today_at_date_8ac30d6": {
"message": "Dziś o godz. { date }"
},
"tomorrow_9a6c9a00": {
"message": "Jutro"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Dziś o godz. { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Nie można oznaczyć jako ukończone."
},
"yesterday_at_date_1aa6d18e": {
"message": "Wczoraj o godz. { date }"
},
"yesterday_c6bd6abf": {
"message": "Wczoraj"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Przewinięto do pierwszej listy zadań!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Adicionar à Lista de Tarefas"
},
"all_items_loaded_aa256183": {
"message": "Todos os itens carregados"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Tem certeza de que deseja excluir este item do planejador?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } está completo"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } está incompleto"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Começo do seu histórico de tarefas"
},
"canvas_planner_98ed106": {
"message": "Planejador do Canvas"
},
"close_d634289d": {
"message": "Fechar"
},
"close_opportunities_popover_f4e50551": {
"message": "Fechar popover de oportunidades"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# oportunidades}\n one {# oportunidade}\n other {# oportunidades}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Mostrar # item completo}\n other {Mostrar # itens completos}\n}"
},
"course_8a63b4a3": {
"message": "Curso"
},
"course_to_do_bcbbab54": {
"message": "{ course } LISTA DE TAREFAS"
},
"date_at_time_dbdb1b99": {
"message": "{ date } às { time }"
},
"date_ee500367": {
"message": "Data"
},
"date_is_required_8660ec22": {
"message": "Data é necessária"
},
"delete_a6efa79d": {
"message": "Excluir"
},
"details_98a31b68": {
"message": "Detalhes"
},
"dismiss_opportunityname_5995176f": {
"message": "Descartar { opportunityName }"
},
"due_5d7267be": {
"message": "Vencimento:"
},
"due_date_8ce289b6": {
"message": "VENCIMENTO: { date }"
},
"edit_title_72e5a21e": {
"message": "Editar { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Erro ao carregar mais itens"
},
"error_loading_past_items_2881dbb1": {
"message": "Erro ao carregar itens passados"
},
"excused_cf8792eb": {
"message": "Dispensado"
},
"failed_to_delete_to_do_64edff49": {
"message": "Falhou ao excluir lista de tarefas"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Falhou ao obter nova atividade"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Falha ao carregar oportunidades."
},
"failed_to_save_to_do_ddc7503b": {
"message": "Falhou ao salvar lista de tarefas"
},
"feedback_6dcc1991": {
"message": "Feedback"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Vá para a Visualização de Cartão do Painel"
},
"graded_25cd3fcd": {
"message": "Avaliado"
},
"late_2be42b88": {
"message": "Tarde"
},
"load_more_a36f9cf9": {
"message": "Carregar mais"
},
"load_prior_dates_f2b0f6f0": {
"message": "Carregar datas anteriores"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Carregou { count, plural,\n =0{# itens}\n one {# item}\n other {# itens}\n}"
},
"loading_25990131": {
"message": "Carregando..."
},
"loading_past_items_ca499e75": {
"message": "Carregando itens passados"
},
"loading_planner_items_947a813d": {
"message": "Carregando itens do planejador"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Parece que não há nada aqui"
},
"missing_1a256b3b": {
"message": "Faltando"
},
"missing_items_for_5ef30cea": {
"message": "Itens ausentes para "
},
"new_activity_8b84847d": {
"message": "Nova atividade"
},
"new_activity_for_ac84f6e6": {
"message": "Nova atividade para "
},
"next_month_749b1778": {
"message": "Próximo mês"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Nenhuma data de vencimento atribuída"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Ainda não há lista de tarefas para hoje."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nada novo precisa de atenção."
},
"optional_add_course_ef0d70fc": {
"message": "Opcional: Adicionar Curso"
},
"points_bceb5005": {
"message": "pontos"
},
"points_points_63e59cce": {
"message": "{ points } pontos"
},
"previous_month_bb1e3c84": {
"message": "Mês anterior"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Respostas"
},
"save_11a80ec3": {
"message": "Salvar"
},
"submitted_318fad53": {
"message": "Enviado"
},
"task_16b0ef38": {
"message": "Tarefa"
},
"title_ee03d132": {
"message": "Título"
},
"title_is_required_6ddcab69": {
"message": "título é necessário"
},
"to_do_1d554f36": {
"message": "Lista de Tarefas"
},
"today_76e10f9c": {
"message": "Hoje"
},
"today_at_date_8ac30d6": {
"message": "Hoje às { date }"
},
"tomorrow_9a6c9a00": {
"message": "Amanhã"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Amanhã às { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Incapaz de marcar como concluído."
},
"yesterday_at_date_1aa6d18e": {
"message": "Ontem às { date }"
},
"yesterday_c6bd6abf": {
"message": "Ontem"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Você voltou para as suas primeiras tarefas!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Adicionar A Fazer"
},
"all_items_loaded_aa256183": {
"message": "Todos os itens carregados"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Tem a certeza que deseja apagar este item de planeador?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } está completo"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } está incompleto"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Início do seu histórico de tarefas"
},
"canvas_planner_98ed106": {
"message": "Planeador Canvas"
},
"close_d634289d": {
"message": "Fechar"
},
"close_opportunities_popover_f4e50551": {
"message": "Fechar oportunidades popover"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# oportunidades}\n one {# oportunidade}\n other {# oportunidades}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Mostrar #item completo}\n other {Mostrar #itens completos}\n}"
},
"course_8a63b4a3": {
"message": "Disciplina"
},
"course_to_do_bcbbab54": {
"message": "{ course } A FAZER"
},
"date_at_time_dbdb1b99": {
"message": "{ date } em { time }"
},
"date_ee500367": {
"message": "Data"
},
"date_is_required_8660ec22": {
"message": "Data requerida"
},
"delete_a6efa79d": {
"message": "Eliminar"
},
"details_98a31b68": {
"message": "Detalhes"
},
"dismiss_opportunityname_5995176f": {
"message": "Ignorar { opportunityName }"
},
"due_5d7267be": {
"message": "Devido:"
},
"due_date_8ce289b6": {
"message": "VENCIMENTO: { date }"
},
"edit_title_72e5a21e": {
"message": "Editar { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Erro ao carregar mais itens"
},
"error_loading_past_items_2881dbb1": {
"message": "Erro ao carregar últimos itens"
},
"excused_cf8792eb": {
"message": "Desculpado"
},
"failed_to_delete_to_do_64edff49": {
"message": "Falha ao eliminar tarefa"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Falha ao obter nova atividade"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Falha ao carregar oportunidades"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Falha ao guardar tarefa"
},
"feedback_6dcc1991": {
"message": "Resposta"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Vá para Vista do cartão do painel"
},
"graded_25cd3fcd": {
"message": "Classificado"
},
"late_2be42b88": {
"message": "Atrasado"
},
"load_more_a36f9cf9": {
"message": "Carregar mais"
},
"load_prior_dates_f2b0f6f0": {
"message": "Carregar datas anteriores"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Itens { count, plural,\n =0{# carregados}\n one {# item}\n other {# itens}\n}"
},
"loading_25990131": {
"message": "A carregar..."
},
"loading_past_items_ca499e75": {
"message": "A carregar itens passados"
},
"loading_planner_items_947a813d": {
"message": "A carregar itens do planeador"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Parece que não há nada aqui"
},
"missing_1a256b3b": {
"message": "Em falta"
},
"missing_items_for_5ef30cea": {
"message": "Itens faltantes para "
},
"new_activity_8b84847d": {
"message": "Nova atividade"
},
"new_activity_for_ac84f6e6": {
"message": "Nova atividade para "
},
"next_month_749b1778": {
"message": "Próximo mês"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Nenhuma data de vencimento atribuída"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Nenhuma \"Tarefa\" para este dia ainda"
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Nada de novo precisa de atenção"
},
"optional_add_course_ef0d70fc": {
"message": "Opcional: Adicionar Disciplina"
},
"points_bceb5005": {
"message": "pontos"
},
"points_points_63e59cce": {
"message": "{ points } pontos"
},
"previous_month_bb1e3c84": {
"message": "Mês anterior"
},
"pts_699bd9aa": {
"message": "pts"
},
"replies_4a8577c8": {
"message": "Respostas"
},
"save_11a80ec3": {
"message": "Guardar"
},
"submitted_318fad53": {
"message": "Entregue"
},
"task_16b0ef38": {
"message": "Tarefa"
},
"title_ee03d132": {
"message": "Título"
},
"title_is_required_6ddcab69": {
"message": "título é obrigatório"
},
"to_do_1d554f36": {
"message": "A Fazer"
},
"today_76e10f9c": {
"message": "Hoje"
},
"today_at_date_8ac30d6": {
"message": "Hoje pelas { date }"
},
"tomorrow_9a6c9a00": {
"message": "Amanhã"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Amanhã pelas { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Não é possível marcar como concluído."
},
"yesterday_at_date_1aa6d18e": {
"message": "Ontem pelas { date }"
},
"yesterday_c6bd6abf": {
"message": "Ontem"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Você percorreu de volta sua primeira Tarefa!"
}
}

View File

@ -0,0 +1,29 @@
{
"close_d634289d": {
"message": "Închide"
},
"course_8a63b4a3": {
"message": "Curs"
},
"date_ee500367": {
"message": "Dată"
},
"delete_a6efa79d": {
"message": "Șterge"
},
"details_98a31b68": {
"message": "Detalii"
},
"load_more_a36f9cf9": {
"message": "Încarcă mai mult"
},
"loading_25990131": {
"message": "Se încarcă..."
},
"save_11a80ec3": {
"message": "Salvează"
},
"title_ee03d132": {
"message": "Titlu"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Добавить в задачи"
},
"all_items_loaded_aa256183": {
"message": "Все элементы загружены"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Вы уверены, что хотите удалить этот элемент планировщика?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } завершено"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } не завершено"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Начало вашей истории задач"
},
"canvas_planner_98ed106": {
"message": "Планировщик Canvas"
},
"close_d634289d": {
"message": "Закрыть"
},
"close_opportunities_popover_f4e50551": {
"message": "Закрыть всплывающее меню дополнительных возможностей"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# возможности}\n one {# возможность}\n other {# возможности}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Показать # завершенное событие}\n other {Показать # завершенные события}\n}"
},
"course_8a63b4a3": {
"message": "Курс"
},
"course_to_do_bcbbab54": {
"message": "{ course } ЗАДАЧИ"
},
"date_at_time_dbdb1b99": {
"message": "{ date } в { time }"
},
"date_ee500367": {
"message": "Дата"
},
"date_is_required_8660ec22": {
"message": "Необходимо указать дату"
},
"delete_a6efa79d": {
"message": "Удалить"
},
"details_98a31b68": {
"message": "Подробные сведения"
},
"dismiss_opportunityname_5995176f": {
"message": "Отклонить { opportunityName }"
},
"due_5d7267be": {
"message": "Срок выполнения:"
},
"due_date_8ce289b6": {
"message": "СРОК: { date }"
},
"edit_title_72e5a21e": {
"message": "Редактировать { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Ошибка загрузки дополнительных элементов"
},
"error_loading_past_items_2881dbb1": {
"message": "Ошибка загрузки прошлых элементов"
},
"excused_cf8792eb": {
"message": "По уважительной причине"
},
"failed_to_delete_to_do_64edff49": {
"message": "Не удалось выполнить удаление"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Не удалось получить новую активность"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Не удалось загрузить варианты"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Не удалось выполнить сохранение"
},
"feedback_6dcc1991": {
"message": "Отзывы"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Перейти к виду карты приборной панели"
},
"graded_25cd3fcd": {
"message": "С оценкой"
},
"late_2be42b88": {
"message": "Поздно"
},
"load_more_a36f9cf9": {
"message": "Загрузить еще"
},
"load_prior_dates_f2b0f6f0": {
"message": "Загрузить предыдущие даты"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Загруженные { count, plural,\n =0{# элементы}\n one {# элемент}\n other {# элементы}\n}"
},
"loading_25990131": {
"message": "Выполняется загрузка..."
},
"loading_past_items_ca499e75": {
"message": "Загрузка прошлых элементов"
},
"loading_planner_items_947a813d": {
"message": "Загрузка событий планировщика"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Похоже, здесь ничего нет"
},
"missing_1a256b3b": {
"message": "Отсутствует"
},
"missing_items_for_5ef30cea": {
"message": "Отсутствуют элементы для "
},
"new_activity_8b84847d": {
"message": "Новая активность"
},
"new_activity_for_ac84f6e6": {
"message": "Новая активность для "
},
"next_month_749b1778": {
"message": "Следующий месяц"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Даты выполнения не заданы"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Задачи на сегодняшний день пока отсутствуют."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Нет новых пунктов, требующих внимания."
},
"optional_add_course_ef0d70fc": {
"message": "Опционально: Добавить курс"
},
"points_bceb5005": {
"message": "баллов"
},
"points_points_63e59cce": {
"message": "{ points } баллов"
},
"previous_month_bb1e3c84": {
"message": "Предыдущий месяц"
},
"pts_699bd9aa": {
"message": "баллы"
},
"replies_4a8577c8": {
"message": "Ответы"
},
"save_11a80ec3": {
"message": "Сохранить"
},
"submitted_318fad53": {
"message": "Отправлено"
},
"task_16b0ef38": {
"message": "Задача"
},
"title_ee03d132": {
"message": "Заголовок"
},
"title_is_required_6ddcab69": {
"message": "необходим заголовок"
},
"to_do_1d554f36": {
"message": "Задачи"
},
"today_76e10f9c": {
"message": "Сегодня"
},
"today_at_date_8ac30d6": {
"message": "Сегодня в { date }"
},
"tomorrow_9a6c9a00": {
"message": "Завтра"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Завтра в { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Невозможно отметить как завершенный."
},
"yesterday_at_date_1aa6d18e": {
"message": "Вчера в { date }"
},
"yesterday_c6bd6abf": {
"message": "Вчера"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Вы перешли к самой первой своей задаче!"
}
}

View File

@ -0,0 +1,32 @@
{
"close_d634289d": {
"message": "Mbyll"
},
"course_8a63b4a3": {
"message": "Kursi"
},
"date_ee500367": {
"message": "Data"
},
"delete_a6efa79d": {
"message": "Fshij"
},
"details_98a31b68": {
"message": "Detajet"
},
"due_5d7267be": {
"message": "Afati Kohor:"
},
"loading_25990131": {
"message": "Duke u ngarkuar..."
},
"save_11a80ec3": {
"message": "Ruaj"
},
"title_ee03d132": {
"message": "Titulli"
},
"to_do_1d554f36": {
"message": "Për të bërë"
}
}

View File

@ -0,0 +1,38 @@
{
"close_d634289d": {
"message": "Zatvori"
},
"course_8a63b4a3": {
"message": "Kurs"
},
"date_ee500367": {
"message": "Datum"
},
"delete_a6efa79d": {
"message": "Izbriši"
},
"details_98a31b68": {
"message": "Detalji"
},
"due_5d7267be": {
"message": "Rok:"
},
"load_more_a36f9cf9": {
"message": "Otključati više"
},
"loading_25990131": {
"message": "Učitavanje..."
},
"pts_699bd9aa": {
"message": "bod"
},
"save_11a80ec3": {
"message": "Sačuvaj"
},
"title_ee03d132": {
"message": "Naslov"
},
"to_do_1d554f36": {
"message": "Uraditi"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "Lägg till att göra"
},
"all_items_loaded_aa256183": {
"message": "Alla föremål laddade"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "Är du säker på att du vill radera det här planeringsföremålet?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } är färdig"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } är ofullständig"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "Början av din To-Do historik"
},
"canvas_planner_98ed106": {
"message": "Canvas planering"
},
"close_d634289d": {
"message": "Stäng"
},
"close_opportunities_popover_f4e50551": {
"message": "Stäng möjligheter"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# Möjligheter}\n one {# möjlighet}\n other {# möjligheter}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {Visa # färdiga föremål}\n other {Visa # färdiga föremål}\n}"
},
"course_8a63b4a3": {
"message": "Kurs"
},
"course_to_do_bcbbab54": {
"message": "{ course } ATT GÖRA"
},
"date_at_time_dbdb1b99": {
"message": "{ date } vid { time }"
},
"date_ee500367": {
"message": "Datum"
},
"date_is_required_8660ec22": {
"message": "Datum krävs"
},
"delete_a6efa79d": {
"message": "Ta bort"
},
"details_98a31b68": {
"message": "Detaljer"
},
"dismiss_opportunityname_5995176f": {
"message": "Avfärda { opportunityName }"
},
"due_5d7267be": {
"message": "Färdig:"
},
"due_date_8ce289b6": {
"message": "FÄRDIG: { date }"
},
"edit_title_72e5a21e": {
"message": "Redigera { title }"
},
"error_loading_more_items_3f109d9f": {
"message": "Det gick inte att läsa in fler föremål"
},
"error_loading_past_items_2881dbb1": {
"message": "Det gick inte att läsa in tidigare föremål"
},
"excused_cf8792eb": {
"message": "Ursäktad"
},
"failed_to_delete_to_do_64edff49": {
"message": "Misslyckades att ta bort: att göra"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "Misslyckades att få ny aktivitet"
},
"failed_to_load_opportunities_52ab6404": {
"message": "Kunde inte ladda upp möjligheter"
},
"failed_to_save_to_do_ddc7503b": {
"message": "Misslyckades att spara: att göra"
},
"feedback_6dcc1991": {
"message": "Återkoppling"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Gå till kortvyn för panelen"
},
"graded_25cd3fcd": {
"message": "Betygsatt"
},
"late_2be42b88": {
"message": "Sen"
},
"load_more_a36f9cf9": {
"message": "Ladda mer"
},
"load_prior_dates_f2b0f6f0": {
"message": "Laddar tidigare datum"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "Laddade { count, plural,\n =0{# föremål}\n one {# föremål}\n other {# föremål}\n}"
},
"loading_25990131": {
"message": "Laddar..."
},
"loading_past_items_ca499e75": {
"message": "Laddar tidigare föremål"
},
"loading_planner_items_947a813d": {
"message": "Laddar planerade föremål"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "Det verkar som att det inte finns något här"
},
"missing_1a256b3b": {
"message": "Saknas"
},
"missing_items_for_5ef30cea": {
"message": "Saknade föremål för "
},
"new_activity_8b84847d": {
"message": "Ny aktivitet"
},
"new_activity_for_ac84f6e6": {
"message": "Ny aktivitet för "
},
"next_month_749b1778": {
"message": "Nästa månad"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "Inga slutdatum tilldelade"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "Inga \"To-Do''s\" för den här dagen ännu."
},
"nothing_new_needs_attention_3ac548d4": {
"message": "Ingenting är i behov av uppmärksamhet"
},
"optional_add_course_ef0d70fc": {
"message": "Valfritt: Lägg till kurs"
},
"points_bceb5005": {
"message": "poäng"
},
"points_points_63e59cce": {
"message": "{ points } poäng"
},
"previous_month_bb1e3c84": {
"message": "Föregående månad"
},
"pts_699bd9aa": {
"message": "poäng"
},
"replies_4a8577c8": {
"message": "Svar"
},
"save_11a80ec3": {
"message": "Spara"
},
"submitted_318fad53": {
"message": "Inlämnad"
},
"task_16b0ef38": {
"message": "Uppgift"
},
"title_ee03d132": {
"message": "Titel"
},
"title_is_required_6ddcab69": {
"message": "rubrik krävs"
},
"to_do_1d554f36": {
"message": "Att göra"
},
"today_76e10f9c": {
"message": "Idag"
},
"today_at_date_8ac30d6": {
"message": "Idag vid { date }"
},
"tomorrow_9a6c9a00": {
"message": "Imorgon"
},
"tomorrow_at_date_b53f2cf1": {
"message": "Imorgon vid { date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "Kunde inte markera som fullständigt."
},
"yesterday_at_date_1aa6d18e": {
"message": "Igår vid { date }"
},
"yesterday_c6bd6abf": {
"message": "Igår"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "Du har skrollat tillbaka till din allra första To-Do!"
}
}

View File

@ -0,0 +1,20 @@
{
"close_d634289d": {
"message": "Ýap"
},
"course_8a63b4a3": {
"message": "Kurs"
},
"date_ee500367": {
"message": " Sene"
},
"delete_a6efa79d": {
"message": "Poz"
},
"loading_25990131": {
"message": "Ýüklenýär..."
},
"save_11a80ec3": {
"message": "sakla"
}
}

View File

@ -0,0 +1,101 @@
{
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } tamamlanmadı"
},
"canvas_planner_98ed106": {
"message": "Canvas Planlayıcı"
},
"close_d634289d": {
"message": "Kapat"
},
"course_8a63b4a3": {
"message": "Ders"
},
"date_ee500367": {
"message": "Tarih"
},
"delete_a6efa79d": {
"message": "Sil"
},
"details_98a31b68": {
"message": "Ayrıntılar"
},
"due_5d7267be": {
"message": "Bitiş:"
},
"excused_cf8792eb": {
"message": "Mazeretli"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "Kontrol Paneli Kartı Görünümüne Git"
},
"graded_25cd3fcd": {
"message": "Notlandırıldı"
},
"late_2be42b88": {
"message": "Geç"
},
"load_more_a36f9cf9": {
"message": "Daha fazla yükle"
},
"loading_25990131": {
"message": "Yükleniyor..."
},
"loading_past_items_ca499e75": {
"message": "Önceki öğeler yükleniyor"
},
"loading_planner_items_947a813d": {
"message": "Planlayıcı öğeleri yükleniyor"
},
"missing_1a256b3b": {
"message": "Eksik"
},
"new_activity_8b84847d": {
"message": "Yeni Etkinlik"
},
"next_month_749b1778": {
"message": "Sonraki Hafta"
},
"optional_add_course_ef0d70fc": {
"message": "Tercihen: Kurs Ekle"
},
"points_bceb5005": {
"message": "puanlar"
},
"points_points_63e59cce": {
"message": "{ points } puan"
},
"previous_month_bb1e3c84": {
"message": "Önceki Hafta"
},
"pts_699bd9aa": {
"message": "not"
},
"save_11a80ec3": {
"message": "Kaydet"
},
"submitted_318fad53": {
"message": "Gönderildi"
},
"task_16b0ef38": {
"message": "Görev"
},
"title_ee03d132": {
"message": "Başlık"
},
"title_is_required_6ddcab69": {
"message": "başlık gereklidir"
},
"to_do_1d554f36": {
"message": "Yapılacaklar"
},
"today_76e10f9c": {
"message": "Bugün"
},
"tomorrow_9a6c9a00": {
"message": "Yarın"
},
"yesterday_c6bd6abf": {
"message": "Dün"
}
}

View File

@ -0,0 +1,68 @@
{
"close_d634289d": {
"message": "Закрити"
},
"course_8a63b4a3": {
"message": "Курс"
},
"date_ee500367": {
"message": "Дата"
},
"delete_a6efa79d": {
"message": "Видалити"
},
"details_98a31b68": {
"message": "Подробиці"
},
"due_5d7267be": {
"message": "Очікується:"
},
"excused_cf8792eb": {
"message": "Поважно"
},
"graded_25cd3fcd": {
"message": "Оцінено"
},
"late_2be42b88": {
"message": "Пізніше"
},
"load_more_a36f9cf9": {
"message": "Завантажити більше"
},
"loading_25990131": {
"message": "Завантаження..."
},
"missing_1a256b3b": {
"message": "Втрачено"
},
"points_bceb5005": {
"message": "бали"
},
"previous_month_bb1e3c84": {
"message": "Попередній місяць"
},
"pts_699bd9aa": {
"message": "бали"
},
"save_11a80ec3": {
"message": "Зберегти"
},
"submitted_318fad53": {
"message": "Підтверджено"
},
"title_ee03d132": {
"message": "Заголовок"
},
"to_do_1d554f36": {
"message": "Зробити"
},
"today_76e10f9c": {
"message": "Сьогодні"
},
"tomorrow_9a6c9a00": {
"message": "Завтра"
},
"yesterday_c6bd6abf": {
"message": "Вчора"
}
}

View File

@ -0,0 +1,29 @@
{
"close_d634289d": {
"message": "Đóng"
},
"course_8a63b4a3": {
"message": "Khóa học"
},
"date_ee500367": {
"message": "Ngày tháng"
},
"delete_a6efa79d": {
"message": "xóa"
},
"details_98a31b68": {
"message": "Chi tiết"
},
"loading_25990131": {
"message": "Đang tải..."
},
"save_11a80ec3": {
"message": "Lưu lại"
},
"title_ee03d132": {
"message": "Tiêu đề"
},
"to_do_1d554f36": {
"message": "Phải làm"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "添加到待办事项"
},
"all_items_loaded_aa256183": {
"message": "已加载所有项目"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "是否确定要删除此规划者项目?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } 已完成"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } 未完成"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "您的待办事项历史记录的开始"
},
"canvas_planner_98ed106": {
"message": "Canvas 计划程序"
},
"close_d634289d": {
"message": "关闭"
},
"close_opportunities_popover_f4e50551": {
"message": "关闭机会弹出窗"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# 机会}\n one {# 机会}\n other {# 机会}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {显示 # 个已完成项目}\n other {显示 # 个已完成项目}\n}"
},
"course_8a63b4a3": {
"message": "课程"
},
"course_to_do_bcbbab54": {
"message": "{ course } 待办事项"
},
"date_at_time_dbdb1b99": {
"message": "{ date } 时间{ time }"
},
"date_ee500367": {
"message": "日期"
},
"date_is_required_8660ec22": {
"message": "日期必填"
},
"delete_a6efa79d": {
"message": "删除"
},
"details_98a31b68": {
"message": "详情"
},
"dismiss_opportunityname_5995176f": {
"message": "撤销{ opportunityName }"
},
"due_5d7267be": {
"message": "到期:"
},
"due_date_8ce289b6": {
"message": "到期:{ date }"
},
"edit_title_72e5a21e": {
"message": "编辑{ title }"
},
"error_loading_more_items_3f109d9f": {
"message": "加载更多项目时出错"
},
"error_loading_past_items_2881dbb1": {
"message": "加载过去的项目时出错"
},
"excused_cf8792eb": {
"message": "已请假"
},
"failed_to_delete_to_do_64edff49": {
"message": "删除待办事项出错"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "获取新活动失败"
},
"failed_to_load_opportunities_52ab6404": {
"message": "加载机遇失败"
},
"failed_to_save_to_do_ddc7503b": {
"message": "保存待办事项出错"
},
"feedback_6dcc1991": {
"message": "反馈"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "转到仪表板卡视图"
},
"graded_25cd3fcd": {
"message": "已评分"
},
"late_2be42b88": {
"message": "延迟"
},
"load_more_a36f9cf9": {
"message": "加载更多"
},
"load_prior_dates_f2b0f6f0": {
"message": "加载以前的日期"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "已加载 { count, plural,\n =0{# 个项目 }\n one {# 个项目 }\n other {# 个项目}\n}"
},
"loading_25990131": {
"message": "加载中……"
},
"loading_past_items_ca499e75": {
"message": "正在加载过去的项目"
},
"loading_planner_items_947a813d": {
"message": "加载计划程序项目"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "此处似乎没有任何内容"
},
"missing_1a256b3b": {
"message": "缺失"
},
"missing_items_for_5ef30cea": {
"message": "缺少项目"
},
"new_activity_8b84847d": {
"message": "新活动"
},
"new_activity_for_ac84f6e6": {
"message": "新活动"
},
"next_month_749b1778": {
"message": "下个月"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "未指定截止日期"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "当天没有“待办事项”。"
},
"nothing_new_needs_attention_3ac548d4": {
"message": "没有任何新内容需要注意。"
},
"optional_add_course_ef0d70fc": {
"message": "可选:添加课程"
},
"points_bceb5005": {
"message": "分值"
},
"points_points_63e59cce": {
"message": "{ points } 分值"
},
"previous_month_bb1e3c84": {
"message": "上个月"
},
"pts_699bd9aa": {
"message": "得分"
},
"replies_4a8577c8": {
"message": "回复"
},
"save_11a80ec3": {
"message": "保存"
},
"submitted_318fad53": {
"message": "已提交"
},
"task_16b0ef38": {
"message": "任务"
},
"title_ee03d132": {
"message": "名称"
},
"title_is_required_6ddcab69": {
"message": "名称必填"
},
"to_do_1d554f36": {
"message": "待办事项"
},
"today_76e10f9c": {
"message": "今天"
},
"today_at_date_8ac30d6": {
"message": "今天{ date }"
},
"tomorrow_9a6c9a00": {
"message": "明天"
},
"tomorrow_at_date_b53f2cf1": {
"message": "明天{ date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "无法标记为完成"
},
"yesterday_at_date_1aa6d18e": {
"message": "昨天{ date }"
},
"yesterday_c6bd6abf": {
"message": "昨天"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "您已经往回滚动到第一个待办事项!"
}
}

View File

@ -0,0 +1,206 @@
{
"add_to_do_7def3c37": {
"message": "添加到待辦事項"
},
"all_items_loaded_aa256183": {
"message": "所有載入項目"
},
"are_you_sure_you_want_to_delete_this_planner_item_b71e330c": {
"message": "是否確定要刪除此手冊項目?"
},
"assignmenttype_title_is_complete_813e54f8": {
"message": "{ assignmentType } { title } 已完成"
},
"assignmenttype_title_is_incomplete_a1d5fac0": {
"message": "{ assignmentType } { title } 未完成"
},
"beginning_of_your_to_do_history_61ef2dce": {
"message": "開始您的待辦事項紀錄"
},
"canvas_planner_98ed106": {
"message": "Canvas 手冊"
},
"close_d634289d": {
"message": "關閉"
},
"close_opportunities_popover_f4e50551": {
"message": "關閉機會彈出視窗"
},
"count_plural_0_opportunities_one_opportunity_other_765e27fa": {
"message": "{ count, plural,\n =0 {# 機會}\n one {# 機會}\n other {# 機會}\n}"
},
"count_plural_one_show_completed_item_other_show_co_8d0d0ae5": {
"message": "{ count, plural,\n one {顯示 # 已完成項目}\n other {顯示 # 已完成項目}\n}"
},
"course_8a63b4a3": {
"message": "課程"
},
"course_to_do_bcbbab54": {
"message": "{ course } 待辦事項"
},
"date_at_time_dbdb1b99": {
"message": "{ date } 於{ time }"
},
"date_ee500367": {
"message": "日期"
},
"date_is_required_8660ec22": {
"message": "日期為必填"
},
"delete_a6efa79d": {
"message": "刪除"
},
"details_98a31b68": {
"message": "詳細資料"
},
"dismiss_opportunityname_5995176f": {
"message": "解除{ opportunityName }"
},
"due_5d7267be": {
"message": "截止日期:"
},
"due_date_8ce289b6": {
"message": "截止日期:{ date }"
},
"edit_title_72e5a21e": {
"message": "編輯{ title }"
},
"error_loading_more_items_3f109d9f": {
"message": "載入更多項目時發生錯誤"
},
"error_loading_past_items_2881dbb1": {
"message": "載入過去項目時發生錯誤"
},
"excused_cf8792eb": {
"message": "已免除"
},
"failed_to_delete_to_do_64edff49": {
"message": "未能刪除待辦事項"
},
"failed_to_get_new_activity_17e7ec7c": {
"message": "未能取得最新活動"
},
"failed_to_load_opportunities_52ab6404": {
"message": "未能加載機會"
},
"failed_to_save_to_do_ddc7503b": {
"message": "未能儲存待辦事項"
},
"feedback_6dcc1991": {
"message": "反饋"
},
"go_to_dashboard_card_view_10398ef6": {
"message": "前往控制面板卡視圖"
},
"graded_25cd3fcd": {
"message": "已評分"
},
"late_2be42b88": {
"message": "逾期"
},
"load_more_a36f9cf9": {
"message": "載入更多"
},
"load_prior_dates_f2b0f6f0": {
"message": "載入先前日期"
},
"loaded_count_plural_0_items_one_item_other_items_e58533e9": {
"message": "已加載 { count, plural,\n =0{# 項目}\n one {# 項目}\n other {# 項目}\n}"
},
"loading_25990131": {
"message": "載入中……"
},
"loading_past_items_ca499e75": {
"message": "正在載入先前項目"
},
"loading_planner_items_947a813d": {
"message": "正在載入手冊項目"
},
"looks_like_there_isn_t_anything_here_d9bcef49": {
"message": "看來這兒甚麼也沒有"
},
"missing_1a256b3b": {
"message": "缺失"
},
"missing_items_for_5ef30cea": {
"message": "缺失項目"
},
"new_activity_8b84847d": {
"message": "新活動"
},
"new_activity_for_ac84f6e6": {
"message": "新活動"
},
"next_month_749b1778": {
"message": "下個月"
},
"no_due_dates_assigned_e8f5bac8": {
"message": "無指定截止日期"
},
"no_to_do_s_for_this_day_yet_90c5873d": {
"message": "當日暫無待辦事項。"
},
"nothing_new_needs_attention_3ac548d4": {
"message": "暫無新事項。"
},
"optional_add_course_ef0d70fc": {
"message": "可選:添加課程"
},
"points_bceb5005": {
"message": "分數"
},
"points_points_63e59cce": {
"message": "{ points } 分數"
},
"previous_month_bb1e3c84": {
"message": "上個月"
},
"pts_699bd9aa": {
"message": "分數"
},
"replies_4a8577c8": {
"message": "回覆"
},
"save_11a80ec3": {
"message": "保存"
},
"submitted_318fad53": {
"message": "已提交"
},
"task_16b0ef38": {
"message": "任務"
},
"title_ee03d132": {
"message": "標題"
},
"title_is_required_6ddcab69": {
"message": "標題為必填"
},
"to_do_1d554f36": {
"message": "待辦事項"
},
"today_76e10f9c": {
"message": "今天"
},
"today_at_date_8ac30d6": {
"message": "於今天{ date }"
},
"tomorrow_9a6c9a00": {
"message": "明天"
},
"tomorrow_at_date_b53f2cf1": {
"message": "於明天{ date }"
},
"unable_to_mark_as_complete_8141856d": {
"message": "無法標記為完成。"
},
"yesterday_at_date_1aa6d18e": {
"message": "於昨天{ date }"
},
"yesterday_c6bd6abf": {
"message": "昨天"
},
"you_ve_scrolled_back_to_your_very_first_to_do_29374681": {
"message": "您已然回到您的第一個待辦事項!"
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) <%= YEAR %> - present Instructure, Inc.
*
* This module is part of Canvas.
*
* This module and Canvas are free software: you can redistribute them and/or modify them under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* This module and Canvas are distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/

View File

@ -0,0 +1,12 @@
version: '2'
services:
test:
build:
context: .
environment:
- NODE_ENV=test
- BABEL_ENV=test
volumes:
- "coverage:/usr/src/app/coverage"
volumes:
coverage:

View File

@ -0,0 +1,60 @@
const moment = require('moment-timezone');
module.exports = (req, res, next) => {
// For VMs, uncomment this line and modify as needed to get correct link header for paging
// req.headers.host = '10.0.2.2:3004';
if (req.query.start_date) {
const due_at_gte = moment(req.query.start_date).tz('UTC');
req.query['plannable.due_at_gte'] = due_at_gte.format();
delete req.query.start_date;
// If you only want to return one day at a time, uncomment this clause
// putting this in will break new activity because it queries for new activity in the deep past
// if (!req.query.end_date) {
// const due_at_lte = due_at_gte.clone().add(1, 'days');
// req.query['plannable.due_at_lte'] = due_at_lte.format();
// }
}
if (req.query.end_date) {
const due_at_lte = moment(req.query.end_date).tz('UTC');
req.query['plannable.due_at_lte'] = due_at_lte.format();
delete req.query.end_date;
// If you only want to return one day at a time, uncomment this clause
// keeping this in for now because _sort doesn't work on nested data, like 'plannable.due_at'
// if (!req.query.start_date) {
// const due_at_gte = due_at_lte.clone().add(-1, 'days');
// req.query['plannable.due_at_gte'] = due_at_gte.format();
// }
if (!req.query.start_date) {
req.query._sort = 'fake_date_dont_use_me_only_for_sorting';
req.query._order = 'DESC';
}
}
// this should probably be in separate pagination middleware. meh.
let prefix = '&';
if (Object.keys(req.query).length === 0) prefix = '?';
if (!req.query._page) {
req.query._page = '1';
// have to muck with originalUrl because that's how the Link header is generated
req.originalUrl = `${req.originalUrl}${prefix}_page=${req.query._page}`;
prefix = '&';
}
if (!req.query._limit) {
req.query._limit = '2';
// ditto
req.originalUrl = `${req.originalUrl}${prefix}_limit=${req.query._limit}`;
prefix = '&';
}
if (req.query.filter && req.query.filter === 'new_activity') {
req.query['status.has_feedback'] = 'true';
delete req.query.filter;
}
next();
};

View File

@ -0,0 +1,149 @@
/**
* This file generates the data used by our json-server.
*/
const moment = require('moment-timezone');
const {
createFakeAssignment,
createFakeDiscussion,
createFakeQuiz,
generateStatus,
generateActivity,
createFakeOpportunity,
createFakeOverride
} = require('./utils');
module.exports = () => {
const data = {
users: [
{
id: 1,
}
],
missing_submissions: [createFakeOpportunity(), createFakeOpportunity(), createFakeOpportunity()],
overrides: {planner_override: {id:1, plannable_id: 1, blah: "lsakdjasdlkjf"}},
planner: {},
items: [
// 2 days ago
createFakeAssignment(
"Java War",
"1",
moment().subtract(2, 'days').endOf('day'),
false,
generateStatus({ missing: true, has_feedback: true })
),
createFakeAssignment(
"C++ War",
"1",
moment().subtract(2, 'days').endOf('day'),
false,
generateStatus({ missing: true, has_feedback: true })
),
createFakeQuiz(
"War of the Language",
"2",
moment().subtract(2, 'days').endOf('day'),
false,
generateStatus({ missing: true, has_feedback: true })
),
// yesterday
createFakeQuiz(
"History Prequiz",
"1",
moment().subtract(1, 'days').startOf('day').add(17, 'hours'),
false,
generateStatus({})
),
createFakeAssignment(
"The Role of Pokémon in Ancient Rome",
"1",
moment().subtract(1, 'days').startOf('day').add(17, 'hours'),
false,
generateStatus({})
),
createFakeAssignment(
"The Great Migration",
"1",
moment().subtract(1, 'days').startOf('day').add(17, 'hours'),
false,
generateStatus({})
),
// today
createFakeAssignment(
"English Civil Wars",
"1",
moment().endOf('day'),
true,
generateStatus()
),
createFakeAssignment(
"War of Jenkins Ear",
"1",
moment().endOf('day'),
false,
generateStatus({ submitted: true, needs_grading: true })
),
createFakeDiscussion(
"Marked Complete when missing",
"1",
moment().endOf('day'),
false,
generateStatus({missing: true, has_feedback: true}),
createFakeOverride("1", "discussion_topic", "1", "true")
),
createFakeQuiz(
"Marked incomplete when submitted",
"1",
moment().endOf('day'),
true,
generateStatus({has_feedback: true}),
createFakeOverride(2, "quiz", "1", "false")
),
createFakeAssignment("English Poetry and Prose", "2", moment().endOf('day')),
createFakeAssignment("English Drama", "2", moment().endOf('day')),
createFakeAssignment("English Fiction", "2", moment().endOf('day')),
// tomorrow
createFakeAssignment(
"Great Turkish War",
"1",
moment().endOf('day').add(1, 'days'),
true,
generateStatus()
),
createFakeAssignment(
"Seven Years War",
"1",
moment().endOf('day').add(1, 'days'),
true,
generateStatus()
),
createFakeAssignment(
"American Revolution",
"1",
moment().endOf('day').add(1, 'days'),
true,
generateStatus({ graded: true })
),
createFakeQuiz("Shakespeare", "2", moment().startOf('day').add(1, 'days').add(8, 'hours')),
createFakeDiscussion("English Short Stories", "2", moment().endOf('day').add(1, 'days')),
// the day after tomorrow
createFakeDiscussion(
"Which revolution is your favorite?",
"1",
moment().endOf('day').add(2, 'days'),
false,
generateStatus()
),
]
};
return data;
};

View File

@ -0,0 +1,31 @@
const { getKindaUniqueId } = require('./utils');
module.exports = (req, res, next) => {
if ((req.method === 'POST') && (/planner_notes/.test(req.originalUrl))) {
const originalBody = req.body;
const id = getKindaUniqueId();
const newBody = {
id, // Not part of the canvas spec, but required for json-server
type: 'viewing',
ignore: `/api/v1/users/self/todo/planner_note_${id}/viewing?permanent=0`,
ignore_permanently: `/api/v1/users/self/todo/planner_note_${id}/viewing?permanent=1`,
visible_in_planner: true,
planner_override: null,
submissions: false,
plannable_type: 'planner_note',
plannable: {
id,
todo_date: originalBody.todo_date,
due_at: originalBody.todo_date, // Not part of the canvas spec, but required for json-server to handle filtering, etc.
title: originalBody.title,
details: originalBody.details,
user_id: '1',
course_id: originalBody.course_id,
workflow_state: 'active',
},
html_url: `/api/v1/planner_notes.${id}`
};
req.body = newBody;
}
next();
};

View File

@ -0,0 +1,30 @@
const { getKindaUniqueId } = require('./utils');
module.exports = (req, res, next) => {
if ((req.method === 'POST') && (/planner\/overrides/.test(req.originalUrl))) {
const originalBody = req.body;
const id = getKindaUniqueId();
const newBody = {
id, // Not part of the canvas spec, but required for json-server
type: 'viewing',
ignore: `/api/v1/users/self/todo/planner_note_${id}/viewing?permanent=0`,
ignore_permanently: `/api/v1/users/self/todo/planner_note_${id}/viewing?permanent=1`,
visible_in_planner: true,
planner_override: null,
submissions: false,
plannable_type: 'planner_note',
plannable: {
id,
todo_date: originalBody.todo_date,
title: originalBody.title,
details: originalBody.details,
user_id: '1',
course_id: originalBody.course_id,
workflow_state: 'active',
},
html_url: `/api/v1/planner_notes.${id}`
};
req.body = newBody;
}
next();
};

View File

@ -0,0 +1,6 @@
{
"/planner/": "/",
"/users/self/missing_submissions": "/missing_submissions?userId=1",
"/planner_notes": "/items",
"/planner/overrides/:id": "/planner/overrides?id=1"
}

View File

@ -0,0 +1,36 @@
const generateData = require('./db');
const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router(generateData());
const rewriter = jsonServer.rewriter(require('./routes.json'));
const middlewares = jsonServer.defaults();
const pause = require('connect-pause');
const dateRewriter = require('./dateRewriter');
const plannerNoteRewriter = require('./plannerNoteRewriter');
const plannerOpportunityRewriter = require('./plannerOpportunityRewriter');
// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);
// To handle POST, PUT and PATCH
server.use(jsonServer.bodyParser);
// Use the routes.json file for custom routes re-routing
server.use(rewriter);
// Slow things down
server.use(pause(1500));
// Custom middlewares
server.use(dateRewriter);
server.use(plannerNoteRewriter);
server.use(plannerOpportunityRewriter);
// Use default router
server.use(router);
server.listen(3004, () => {
console.log('JSON Server is running');
});

View File

@ -0,0 +1,378 @@
const moment = require('moment-timezone');
const getKindaUniqueId = () => Math.floor(Math.random() * (100000 - 1) + 1).toString();
const generateStatus = (overrides) => {
const statusObject = {
excused: false,
graded: false,
late: false,
submitted: false,
missing: false,
needs_grading: false,
has_feedback: false
};
if (overrides) {
return Object.assign({}, statusObject, overrides);
}
const baseStatusDecider = Math.floor(Math.random() * (10000 - 1)) % 4;
switch (baseStatusDecider) {
case 0:
statusObject.graded = true;
if (Math.random() > 0.5) {
statusObject.late = true;
}
if (Math.random() < 0.5) {
statusObject.has_feedback = true;
}
break;
case 1:
statusObject.excused = true;
break;
case 2:
statusObject.submitted = true;
if (Math.random() > 0.5) {
statusObject.late = true;
}
if (Math.random() < 0.5) {
statusObject.needs_grading = true;
}
break;
default:
if (Math.random() > 0.5) {
statusObject.missing = true;
}
break;
}
return statusObject;
};
const createFakeAssignment = (name, courseId = "1", dueDateTime = moment(), completed = false, status = false) => {
const id = getKindaUniqueId();
return {
id: id, // This is NOT part of the Canvas API but is required for JSON Server
fake_date_dont_use_me_only_for_sorting: dueDateTime.tz('UTC').format(),
context_type: "Course",
course_id: courseId,
type: "submitting",
ignore: `/api/v1/users/self/todo/assignment_${id}/submitting?permanent=0`,
ignore_permanently: `/api/v1/users/self/todo/assignment_${id}/submitting?permanent=1`,
visible_in_planner: true,
planner_override: null,
plannable_type: 'assignment',
submissions: status,
plannable: {
id: id,
description: "<p>Lorem ipsum etc.</p>",
due_at: dueDateTime.tz('UTC').format(),
unlock_at: null,
lock_at: null,
points_possible: 50,
grading_type: "points",
assignment_group_id: "2",
grading_standard_id: null,
created_at: "2017-05-12T15:05:48Z",
updated_at: "2017-05-12T15:05:48Z",
peer_reviews: false,
automatic_peer_reviews: false,
position: 1,
grade_group_students_individually: false,
anonymous_peer_reviews: false,
group_category_id: null,
post_to_sis: false,
moderated_grading: false,
omit_from_final_grade: false,
intra_group_peer_reviews: false,
secure_params: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
course_id: courseId,
name: name,
submission_types: [
"online_text_entry",
"online_upload"
],
has_submitted_submissions: completed,
due_date_required: false,
max_name_length: 255,
in_closed_grading_period: false,
is_quiz_assignment: false,
muted: false,
html_url: `/courses/${courseId}/assignments/${id}`,
published: true,
only_visible_to_overrides: false,
locked_for_user: false,
submissions_download_url: `/courses/${courseId}/assignments/${id}/submissions?zip=1`
},
html_url: `/courses/${courseId}/assignments/${id}#submit`
};
};
const createFakeDiscussion = (name, courseId = "1", dueDateTime = moment(), completed = false, status = false) => {
const id = getKindaUniqueId();
return {
id: id, // This is NOT part of the Canvas API but is required for JSON Server
fake_date_dont_use_me_only_for_sorting: dueDateTime.tz('UTC').format(),
context_type: "Course",
course_id: courseId,
type: "submitting",
ignore: `/api/v1/users/self/todo/assignment_${id}/submitting?permanent=0`,
ignore_permanently: `/api/v1/users/self/todo/assignment_${id}/submitting?permanent=1`,
visible_in_planner: true,
planner_override: null,
plannable_type: 'discussion_topic',
submissions: status,
plannable: {
id: "1",
title: name,
last_reply_at: "2017-05-15T16:32:34Z",
delayed_post_at: null,
posted_at: "2017-05-15T16:32:34Z",
assignment_id: id,
root_topic_id: null,
position: null,
podcast_has_student_posts: false,
discussion_type: "side_comment",
lock_at: null,
allow_rating: false,
only_graders_can_rate: false,
sort_by_rating: false,
user_name: "clay@instructure.com",
discussion_subentry_count: 0,
permissions: {
attach: false,
update: false,
reply: true,
delete: false
},
require_initial_post: null,
user_can_see_posts: true,
podcast_url: null,
read_state: "unread",
unread_count: 0,
subscribed: false,
topic_children: [],
attachments: [],
published: true,
can_unpublish: false,
locked: false,
can_lock: false,
comments_disabled: false,
author: {
id: "1",
display_name: "Carl Chudyk",
avatar_image_url: "http://canvas.instructure.com/images/messages/avatar-50.png",
html_url: `/courses/${courseId}/users/1`
},
html_url: `/courses/${courseId}/discussion_topics/${id}`,
url: `/courses/${courseId}/discussion_topics/${id}`,
pinned: false,
group_category_id: null,
can_group: true,
locked_for_user: false,
message: "<p>Some prompt</p>",
todo_date: dueDateTime.tz('UTC').format(),
// have to add this because json server doesn't have the capability to do both due_at and todo_date
due_at: dueDateTime.tz('UTC').format(),
},
html_url: `/courses/${courseId}/assignments/${id}#submit`
};
};
const createFakeQuiz = (name, courseId = "1", dueDateTime = moment(), completed = false, status = false) => {
const id = getKindaUniqueId();
return {
id: id, // This is NOT part of the Canvas API but is required for JSON Server
fake_date_dont_use_me_only_for_sorting: dueDateTime.tz('UTC').format(),
context_type: "Course",
course_id: courseId,
type: "submitting",
ignore: `/api/v1/users/self/todo/assignment_${id}/submitting?permanent=0`,
ignore_permanently: `/api/v1/users/self/todo/assignment_${id}/submitting?permanent=1`,
visible_in_planner: true,
planner_override: null,
plannable_type: 'quiz',
submissions: status,
plannable: {
id: "2",
title: name,
html_url: `/courses/${courseId}/quizzes/${id}`,
mobile_url: `/courses/${courseId}/quizzes/${id}?force_user=1\u0026persist_headless=1`,
description: "\u003cp\u003easdfasdf\u003c\/p\u003e",
quiz_type: "assignment",
time_limit: null,
shuffle_answers: false,
show_correct_answers: true,
scoring_policy: "keep_highest",
allowed_attempts: 1,
one_question_at_a_time: false,
question_count: 0,
points_possible: 0.0,
cant_go_back: false,
ip_filter: null,
due_at: dueDateTime.tz('UTC').format(),
lock_at: null,
unlock_at: null,
published: true,
locked_for_user: false,
hide_results: null,
show_correct_answers_at: null,
hide_correct_answers_at: null,
all_dates: [
{
due_at: dueDateTime.tz('UTC').format(),
unlock_at: null,
lock_at: null,
base: true
}
],
can_update: false,
require_lockdown_browser: false,
require_lockdown_browser_for_results: false,
require_lockdown_browser_monitor: false,
lockdown_browser_monitor_data: null,
permissions: {
read_statistics: false,
manage: false,
read: true,
update: false,
create: false,
submit: true,
preview: false,
delete: false,
grade: false,
review_grades: false,
view_answer_audits: false
},
quiz_reports_url: `/courses/${courseId}/quizzes/${id}/reports`,
quiz_statistics_url: `/courses/${courseId}/quizzes/${id}/statistics`,
quiz_submission_versions_html_url: `/courses/${courseId}/quizzes/${id}/submission_versions`,
assignment_id: 6,
one_time_results: false,
assignment_group_id: 1,
show_correct_answers_last_attempt: false,
version_number: 2,
question_types: [],
has_access_code: false,
post_to_sis: false
},
html_url: `/courses/${courseId}/quizzes/${id}`
};
};
const createFakeWiki = (name, courseId = "1", dueDateTime = moment(), completed = false, status = false) => {
const id = getKindaUniqueId();
return {
fake_date_dont_use_me_only_for_sorting: dueDateTime.tz('UTC').format(),
context_type: 'Course',
course_id: courseId,
type: 'viewing',
ignore: `/api/v1/users/self/todo/wiki_page_${id}/viewing?permanent=0`,
ignore_permanently: `/api/v1/users/self/todo/wiki_page_${id}/viewing?permanent=1`,
visible_in_planner: true,
planner_override: null,
plannable_type: 'wiki_page',
submissions: status,
plannable: {
id: "1",
title: name,
created_at: '2017-06-05T14:48:47Z',
url: 'bgg-wiki',
editing_roles: 'teachers',
page_id: id,
last_edited_by: {
id: 1,
display_name: 'Carl',
avatar_image_url: 'http://canvas.instructure.com/images/messages/avatar-50.png',
html_url: '/courses/1/users/1'
},
published: true,
hide_from_students: false,
front_page: false,
html_url: '/courses/1/pages/bgg-wiki',
todo_date: dueDateTime.tz('UTC').format(),
// have to add this because json server doesn't have the capability to do both due_at and todo_date
due_at: dueDateTime.tz('UTC').format(),
updated_at: '2017-06-05T14:48:47Z',
locked_for_user: false,
body: ''
},
html_url: 'bgg-wiki'
};
};
const createFakeOpportunity = (description = "Random Description", courseId = "1", dueDateTime = moment()) => {
const id = getKindaUniqueId();
return {
id: id,
description: description,
due_at: "2017-05-12T15:05:48Z",
unlock_at: null,
lock_at: null,
points_possible: 0,
grading_type: "points",
assignment_group_id: 1,
grading_standard_id: null,
created_at: "2017-03-09T20:40:35Z",
updated_at: "2017-04-20T04:02:18Z",
peer_reviews: false,
automatic_peer_reviews: false,
position: 3,
grade_group_students_individually: false,
anonymous_peer_reviews: false,
group_category_id: null,
post_to_sis: false,
moderated_grading: false,
omit_from_final_grade: false,
intra_group_peer_reviews: false,
secure_params: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsdGlfYXNzaWdubWVudF9pZCI6ImM3MDA1NzAyLTNhZTItNGFiOC05ZTkzLWJhNGY2ZmE1OTA0ZSJ9.3CTWn4eMV8jhXsnGc0u0WndwFtzSls9V8Ge2h8wdUc8",
course_id: courseId,
name: "Quiz 2",
submission_types: [
"online_quiz"
],
has_submitted_submissions: false,
due_date_required: false,
max_name_length: 255, in_closed_grading_period: false,
is_quiz_assignment: true,
muted: false,
html_url: "http://localhost:3000/courses/1/assignments/8",
quiz_id: 4,
anonymous_submissions: false,
published: true,
only_visible_to_overrides: false,
locked_for_user: false,
submissions_download_url: "http://localhost:3000/courses/1/quizzes/4/submissions?zip=1"
};
};
const createFakeOverride = (plannableId = "1", plannableType = "assignment", userId = "1", completed = "false") => {
return {
id: getKindaUniqueId(),
plannable_type: plannableType,
plannable_id: plannableId,
user_id: userId,
workflow_state: "active",
deleted_at: null,
created_at: "2017-03-09T20:40:35Z",
updated_at: "2017-04-20T04:02:18Z",
marked_complete: completed,
};
};
module.exports = {
createFakeAssignment,
createFakeDiscussion,
createFakeQuiz,
createFakeWiki,
getKindaUniqueId,
generateStatus,
createFakeOpportunity,
createFakeOverride,
};

View File

@ -0,0 +1 @@
require('@instructure/ui-themes/lib/canvas');

View File

@ -0,0 +1,11 @@
var proxy = require('identity-obj-proxy');
var transform = require('@instructure/babel-plugin-themeable-styles/transform');
/*
This is a babel transform to mock the themeable styles objects for the jest tests.
See package.json for the jest config.
*/
module.exports = {
process: function(src, filename) {
return 'module.exports = ' + transform(proxy, src);
}
};

View File

@ -0,0 +1,31 @@
module.exports = {
transform: {
'^.+\\.(js)$': 'babel-jest',
'^.+\\.(css)$': '<rootDir>/jest-themeable-styles'
},
snapshotSerializers: [
'enzyme-to-json/serializer'
],
setupFiles: [
'./jest-env.js'
],
coverageReporters: [
'html',
'text'
],
collectCoverageFrom: [
'src/**/*.js'
],
coveragePathIgnorePatterns: [
'<rootDir>/src/demo.js',
'<rootDir>/src/i18n/flip-message.js'
],
coverageThreshold: {
global: {
branches: 85,
functions: 90,
lines: 90,
statements: 90
}
}
};

View File

@ -0,0 +1,107 @@
{
"name": "canvas-planner",
"version": "1.0.16",
"description": "UI portion of planner application for canvas-lms.",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/instructure/canvas-planner.git"
},
"scripts": {
"check-transifex": "BABEL_ENV=test babel-node ./scripts/check-transifex.js",
"extract": "format-message extract $(find src/ -name \"*.js\") -o config/locales/en.json",
"lint-staged": "lint-staged",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"build": "./scripts/build",
"prepublish": "yarn build",
"build:lib": "BABEL_ENV=production babel src --out-dir lib --ignore spec.js,test.js,demo.js --quiet",
"build:es": "BABEL_ENV=production ES_MODULES=1 babel src --out-dir es --ignore spec.js,test.js,demo.js --quiet",
"build:watch": "yarn run build:lib -- --watch",
"start": "npm-run-all --parallel start:*",
"start:json-server": "node ./fake_server/server.js",
"start:webpack-dev": "BABEL_ENV=development webpack-dev-server --port 3005 --host 0.0.0.0",
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch",
"test:update": "jest --updateSnapshot",
"generate-themeable": "./scripts/generate-themeable",
"clean": "rm -rf .babel-cache && rm -rf es && rm -rf lib && rm -rf coverage"
},
"files": [
"config/",
"lib/",
"es/",
"README.md"
],
"keywords": [
"planner",
"canvas"
],
"author": "Instructure, Inc.",
"license": "AGPL-3.0",
"dependencies": {
"@instructure/ui-core": "^4.6.0",
"axios": "^0.16.0",
"babel-plugin-inline-react-svg": "^0.4.0",
"change-case": "^3.0.1",
"classnames": "^2.2.5",
"esrever": "^0.2.0",
"format-message": "^5.1.2",
"format-message-parse": "^5.1.2",
"format-message-print": "^5.1.2",
"instructure-icons": "^4.3.0",
"keycode": "^2.1.9",
"lodash": "^4",
"moment": "~2.10.6",
"moment-timezone": "^0.5.13",
"parse-link-header": "^1.0.1",
"prop-types": "^15.5.9",
"react": "^0.14.7 || ^15",
"react-dom": "^0.14.7 || ^15",
"react-moment-proptypes": "^1.4.0",
"react-redux": "^5.0.3",
"react-waypoint": "^7.0.3",
"redux": "^3.5.2",
"redux-actions": "^2.0.1",
"redux-logger": "^3.0.1",
"redux-promise": "^0.5.3",
"redux-saga": "^0.16.0",
"redux-thunk": "^2.2.0",
"velocity-animate": "^1.5.0"
},
"devDependencies": {
"@instructure/ui-presets": "^4.6.0",
"babel-cli": "^6",
"babel-core": "^6",
"babel-eslint": "^7",
"babel-jest": "^22",
"babel-loader": "^7",
"babel-plugin-transform-class-display-name": "^0.0.3",
"babel-plugin-transform-node-env-inline": "^0.2.0",
"babel-plugin-transform-regenerator": "^6",
"connect-pause": "^0.1.0",
"enzyme": "^2.8.0",
"enzyme-to-json": "^1.5.0",
"eslint": "^4",
"eslint-config-react-app": "^2",
"eslint-loader": "^1",
"eslint-plugin-flowtype": "^2",
"eslint-plugin-import": "^2",
"eslint-plugin-jsx-a11y": "^5",
"eslint-plugin-notice": "^0.2.2",
"eslint-plugin-react": "^7",
"format-message-cli": "^5.1.4",
"identity-obj-proxy": "^3.0.0",
"jest": "^22",
"json-server": "^0.9.6",
"lint-staged": "^3.4.0",
"moxios": "^0.4.0",
"npm-run-all": "^4",
"webpack": "^3",
"webpack-dev-server": "^2"
},
"lint-staged": {
"*.js": "eslint"
}
}

View File

@ -0,0 +1,3 @@
module.exports = require('@instructure/ui-presets/postcss')({
// config
})

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canvas Planner</title>
<style>
body {
/* simulate Canvas' line-height */
line-height: 1.5;
}
#header {
display: flex;
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 20;
}
#demo_only_mount {
flex: .8;
margin-right: 20px;
}
#header_mount_point {
flex: .2;
}
</style>
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
</head>
<body>
<div id="header">
<div id="demo_only_mount"></div>
<div id="header_mount_point"></div>
</div>
<div id="mount_point"></div>
<script src="ie11-polyfill.js"></script>
<script src="canvas-planner.js"></script>
</body>
</html>

View File

@ -0,0 +1,12 @@
#!/bin/bash
echo '############################################################'
echo '# Building fully Babelified CommonJS version of the module #'
echo '############################################################'
yarn run build:lib
# Commenting this out for now since canvas only actually uses the CJS version.
# echo '############################################################'
# echo '# Building fully Babelified ES version of the module #'
# echo '############################################################'
# yarn run build:es

View File

@ -0,0 +1,294 @@
/* eslint no-console: off, semi: off */
import request from 'request'
import { readFile, writeFile, readFileSync } from 'fs'
import { join } from 'path'
import formatMessage from 'format-message'
import parse from 'format-message-parse'
let configFileName = process.argv[2]
if (!configFileName) {
configFileName = '/dev/stdin'
}
const config = JSON.parse(readFileSync(configFileName))
const TRANSIFEX_USERNAME = process.env.TRANSIFEX_USERNAME
const TRANSIFEX_PASSWORD = process.env.TRANSIFEX_PASSWORD
const JIRA_USERNAME = process.env.JIRA_USERNAME
const JIRA_PASSWORD = process.env.JIRA_PASSWORD
function createJIRAIssue (resource, { summary, description }) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: 'https://instructure.atlassian.net/rest/api/2/issue/',
auth: { user: JIRA_USERNAME, pass: JIRA_PASSWORD },
json: { fields: {
project: { key: resource.jiraProjectKey },
summary,
description,
issuetype: { name: resource.jiraIssueType },
assignee: { name: resource.jiraAssigneeName }
} }
}, (err, resp, body) => err ? reject(err) : resolve(body))
})
}
function filenameForStatusOfResource (resource) {
return join(
resource.folder,
`.transifex-status.${resource.projectSlug}.${resource.slug}.json`
)
}
function loadPreviousStatusOfResource (resource) {
return new Promise((resolve, reject) => {
let filename = filenameForStatusOfResource(resource)
readFile(filename, 'utf8', (err, data) => {
if (err) {
return err.code === 'ENOENT' ? resolve({}) : reject(err)
}
resolve(JSON.parse(data))
})
})
}
function writeJSONFile (filename, content) {
return new Promise((resolve, reject) => {
writeFile(filename, JSON.stringify(content, null, ' '), 'utf8', (err) =>
err ? reject(err) : resolve()
)
})
}
function saveStatusOfResource (resource, status) {
return writeJSONFile(filenameForStatusOfResource(resource), status)
}
function filenameForResource (resource, language) {
let dir = resource.folder.replace(/\/+$/, '')
if (resource.localeNameMapping && resource.localeNameMapping[language]) {
language = resource.localeNameMapping[language]
}
let file = language.replace(/_/g, '-') + '.json'
return join(dir, file)
}
function saveTranslationsOfResource (resource, language, translations) {
let sorted = Object.keys(translations).sort().reduce((o, id) => {
o[id] = translations[id]
return o
}, {})
return writeJSONFile(filenameForResource(resource, language), sorted)
}
function getJSON (path) {
return new Promise((resolve, reject) => {
request({
url: `https://www.transifex.com/api/2${path}`,
auth: { user: TRANSIFEX_USERNAME, pass: TRANSIFEX_PASSWORD },
json: true
}, (err, resp, body) => err ? reject(err) : resolve(body))
})
}
function getSourceLastUpdatedDate (resource) {
return getJSON(`/project/${resource.projectSlug}/resource/${resource.slug}?details=1`)
}
function checkStatusOfResource (resource) {
return getJSON(`/project/${resource.projectSlug}/resource/${resource.slug}/stats/`)
}
async function getMessagesOfResource (resource) {
let body = await getJSON(`/project/${resource.projectSlug}/resource/${resource.slug}/content/`)
return JSON.parse(body.content)
}
async function getTranslationsOfResource (resource, language) {
let body = await getJSON(`/project/${resource.projectSlug}/resource/${resource.slug}/translation/${language}/?mode=onlytranslated`)
return JSON.parse(body.content)
}
function createJIRAForNewStrings (resource, languages) {
let statuses = languages.map((status) => (
`*Language: ${status.language}*\n` +
`* Translate: ${status.untranslated_entities} strings (${status.untranslated_words} words)\n` +
`* Review: ${status.translated_entities - status.reviewed} strings`
)).join('\n\n')
let description = `Transifex is reporting that new translations are needed in ${resource.project} ${resource.name}.\n\n${statuses}`
return createJIRAIssue(resource, {
summary: `Translate new strings in ${resource.project} ${resource.name}`,
description
})
}
function getParamsFromPatternAst (ast) {
if (!ast || !ast.slice) return []
let stack = ast.slice()
let params = []
while (stack.length) {
let element = stack.pop()
if (typeof element === 'string') continue
if (element.length === 1 && element[0] === '#') continue
var name = element[0]
if (params.indexOf(name) < 0) params.push(name)
var type = element[1]
if (type === 'select' || type === 'plural' || type === 'selectordinal') {
var children = type === 'select' ? element[2] : element[3]
stack = stack.concat(
...Object.keys(children).map((key) => children[key])
)
}
}
return params
}
function getTranslationErrors (messages, translations) {
let errors = []
for (let id of Object.keys(translations)) {
let translation = translations[id].message
if (!translation) { // unset untranslated messages
translations[id] = undefined
continue
}
// unset translation descriptions, as they are not needed nor used
translations[id].description = undefined
let translationAst
try {
translationAst = parse(translation)
} catch (e) {
let problem = 'Translation is an invalid ICU message pattern.'
errors.push({ id, translation, problem, notes: e.message })
translations[id] = undefined // unset translation with errors
}
if (!translationAst) continue
let translationParams = getParamsFromPatternAst(translationAst)
let source = messages[id].message
let sourceAst = messages[id].ast ||
(messages[id].ast = parse(source))
let sourceParams = messages[id].params ||
(messages[id].params = getParamsFromPatternAst(sourceAst))
let missing = sourceParams.filter((key) => !translationParams.includes(key))
let extra = translationParams.filter((key) => !sourceParams.includes(key))
let problem = []
if (missing.length > 0) {
let params = missing.map((str) => JSON.stringify(str)).join(', ')
problem.push(formatMessage(`{ count, plural,
one {Translation is missing {params} parameter.}
other {Translation is missing {params} parameters.}
}`, { count: missing.length, params }))
}
if (extra.length > 0) {
let params = extra.map((str) => JSON.stringify(str)).join(', ')
problem.push(formatMessage(`{ count, plural,
one {Translation has extra {params} parameter.}
other {Translation has extra {params} parameters.}
}`, { count: extra.length, params }))
}
let notes = ''
if (missing.length > 0 && missing.length === extra.length) {
notes = 'Parameters may have been translated by mistake.'
}
if (problem.length > 0) {
errors.push({ id, translation, problem: problem.join('\n'), notes })
translations[id] = undefined // unset translation with errors
}
}
return errors
}
function handleTranslationErrors (resource, errors) {
if (Object.keys(errors).length === 0) return
let formatLanguage = (language) => (
`*Language: ${language}*\n` +
'||Key||Translation||Problem||Notes||\n' +
formatErrors(errors[language])
)
let formatErrors = (rows) => rows.map(formatError).join('\n')
let formatError = (row) => ('|' + [
row.id,
row.translation,
row.problem,
row.notes || ' '
].map(clean).join('|') + '|')
let clean = (str) => str.replace(/[{}|]/g, '\\$&').replace(/(\r?\n)+/g, '\n')
let statuses = Object.keys(errors).map(formatLanguage).join('\n')
let description = `There were problems during language import in ${resource.project} ${resource.name}.\n\n${statuses}`
return createJIRAIssue(resource, {
summary: `Fix Transifex Import Errors for ${resource.project} ${resource.name}`,
description
})
}
async function handleNewTranslations (resource, languages) {
let messages = await getMessagesOfResource(resource)
let allErrors = {}
for (let language of languages) {
let translations = await getTranslationsOfResource(resource, language)
let errors = getTranslationErrors(messages, translations)
if (errors.length) {
allErrors[language] = errors
}
await saveTranslationsOfResource(resource, language, translations)
}
await handleTranslationErrors(resource, allErrors)
}
async function run () {
for (let resource of config.resources) {
let prevStatus = await loadPreviousStatusOfResource(resource)
let status = await checkStatusOfResource(resource)
let sourceLastUpdated = Date.parse(await getSourceLastUpdatedDate(resource));
let newStrings = []
let newTranslations = []
let sourcePreviouslyUpdated = Date.parse((prevStatus[resource.sourceLanguage] || {}).last_update) || 0
let sourceWasUpdated = sourceLastUpdated > sourcePreviouslyUpdated
for (let language of Object.keys(status).sort()) {
if (language === resource.sourceLanguage) continue
let prevLangStatus = prevStatus[language] || {
untranslated_entities: 0,
translated_entities: 0,
last_update: '2015-12-12 12:34:56'
}
let langStatus = status[language]
let langPreviouslyUpdated = Date.parse(prevLangStatus.last_update)
let langLastUpdated = Date.parse(langStatus.last_update)
let langWasUpdated = langLastUpdated > langPreviouslyUpdated
if (sourceWasUpdated && langStatus.untranslated_entities > 0) {
newStrings.push({ ...langStatus, language })
}
if (langWasUpdated && langStatus.translated_entities > 0) {
newTranslations.push(language)
}
}
let promises = []
if (newStrings.length) {
if (resource.jiraTicketForNewStrings !== false) {
promises.push(createJIRAForNewStrings(resource, newStrings))
}
}
if (newTranslations.length) {
promises.push(handleNewTranslations(resource, newTranslations))
}
promises.push(saveStatusOfResource(resource, status))
await Promise.all(promises)
}
}
run().catch((err) => {
console.error(err.stack)
process.exit(-1)
})

View File

@ -0,0 +1,37 @@
#!/bin/bash -e
COMPONENTS_DIR=src/components
COMPONENT_TEMPLATE_DIR=scripts/templates/themeable
read -p 'Component name: ' component
export COMPONENT=$component
if [ ! -d "$COMPONENTS_DIR" ]; then
echo Cannot find component directory!
exit 1
fi
if [ ! -d "$COMPONENT_TEMPLATE_DIR" ]; then
echo Cannot find component template!
exit 1
fi
if [ -d "$COMPONENTS_DIR/$COMPONENT" ]; then
echo Oops! Component $COMPONENT exists!
exit 1
fi
echo Generating $COMPONENT files in $COMPONENTS_DIR/$COMPONENT
cp -r $COMPONENT_TEMPLATE_DIR $COMPONENTS_DIR/$COMPONENT
# Update file names
find $COMPONENTS_DIR/$COMPONENT -name 'Component*' -type f -exec bash -c 'mv "$1" "${1/Component/$COMPONENT}"' -- {} \;
# Update file contents
if [[ "$OSTYPE" == darwin* ]]; then
find $COMPONENTS_DIR/$COMPONENT -type f -exec sed -i '' "s/\${COMPONENT}/$COMPONENT/g" '{}' \;
else
find $COMPONENTS_DIR/$COMPONENT -type f -exec sed -i -e "s/\${COMPONENT}/$COMPONENT/g" '{}' \;
fi

View File

@ -0,0 +1,67 @@
#!/bin/bash -e
########################################
# Make sure working directory is clean #
########################################
if [ ! -z "$(git status --porcelain)" ]; then
echo Refusing to operate on unclean working directory
echo Use \"git status\" to see which files have been modified
exit 1
fi
#########################
# Determine the version #
#########################
current_version=$(node -p "require('./package').version")
if ! [[ $next_version ]]; then
printf "Enter version or <ENTER> to release $current_version: "
read next_version
fi
if ! [[ $next_version ]]; then
next_version=$current_version
fi
if ! [[ $next_version =~ ^[0-9]\.[0-9]+\.[0-9](-.+)? ]]; then
echo >&2 "Version \"$next_version\" is not valid! It must be a valid semver string like 1.0.2 or 2.3.0-beta.1"
exit 1
fi
echo '############################################################'
echo '# Cleaning up local directory #'
echo '############################################################'
yarn run clean
echo '############################################################'
echo '# Running tests with coverage data #'
echo '############################################################'
yarn run test:coverage
echo '############################################################'
echo '# Building canvas-planner #'
echo '############################################################'
NODE_ENV=production BABEL_ENV=production yarn run build
############################################################
# Fix package.json version #
############################################################
node -p "p=require(\"./package\");p.version=\"$next_version\";JSON.stringify(p,null,2)" > package.publishing.json
mv package.publishing.json package.json
echo '############################################################'
echo '# Publishing to npm #'
echo '############################################################'
npm publish
echo '############################################################'
echo '# Pushing release tag #'
echo '############################################################'
release_tag="v$next_version"
git tag -am "Version $next_version" $release_tag
git push origin $release_tag
echo '############################################################'
echo ' SUCCESSFULLY PUBLISHED '$next_version
echo '############################################################'

View File

@ -0,0 +1,15 @@
import React, { Component } from 'react';
import themeable from '@instructure/ui-themeable/lib';
import styles from './styles.css';
import theme from './theme.js';
class ${COMPONENT} extends Component {
render () {
return (
<div className={styles.root} />
);
}
}
export default themeable(theme, styles)(${COMPONENT});

View File

@ -0,0 +1,10 @@
/* Variables are defined in ./theme.js */
.root {
font-size: var(--fontSize);
font-family: var(--fontFamily);
font-weight: var(--fontWeight);
color: var(--color);
background: var(--background);
}

View File

@ -0,0 +1,12 @@
/* Global variables (colors, typography, spacing, etc.) are defined in lib/themes */
export default function generator ({ colors, typography }) {
return {
fontSize: typography.fontSizeMedium,
fontFamily: typography.fontFamily,
fontWeight: typography.fontWeightNormal,
color: colors.oxford,
background: colors.white
};
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import CanvasPlanner from '../index';
import moxios from 'moxios';
describe('with mock api', () => {
beforeEach(() => {
document.body.innerHTML = '<div id="container"></div>';
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
describe('render', () => {
it('calls render with PlannerApp', () => {
CanvasPlanner.render(document.getElementById('container'), {
flashAlertFunctions: {}
});
expect(document.querySelector('.PlannerApp')).toBeTruthy();
});
it('throws an error if flashAlertFunctions are not provided', () => {
expect(() => {
CanvasPlanner.render(document.getElementById('container'));
}).toThrow();
});
});
describe('renderHeader', () => {
const options = {
ariaHideElement: document.createElement('div')
};
beforeEach(() => {
document.body.innerHTML = '<div id="header_container"></div>';
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('calls render with PlannerHeader', () => {
CanvasPlanner.renderHeader(document.getElementById('header_container'), options);
// This assertion is a bit odd, we need to get class names working with
// CSS modules under test then we can be more clear here.
expect(document.querySelector('#header_container div')).toBeTruthy();
});
});
});

View File

@ -0,0 +1,466 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import * as Actions from '../index';
import moxios from 'moxios';
import moment from 'moment-timezone';
import {isPromise, moxiosWait, moxiosRespond} from '../../test-utils';
import { initialize as alertInitialize } from '../../utilities/alertUtils';
jest.mock('../../utilities/apiUtils', () => ({
transformApiToInternalItem: jest.fn(response => ({...response, transformedToInternal: true})),
transformInternalToApiItem: jest.fn(internal => ({...internal, transformedToApi: true})),
transformInternalToApiOverride: jest.fn(internal => ({...internal.planner_override, marked_complete: null, transformedToApiOverride: true})),
transformPlannerNoteApiToInternalItem: jest.fn(response => ({...response, transformedToInternal: true}))
}));
const getBasicState = () => ({
courses: [],
groups: [],
timeZone: 'UTC',
days: [
['2017-05-22', [{id: '42', dateBucketMoment: moment.tz('2017-05-22', 'UTC')}]],
['2017-05-24', [{id: '42', dateBucketMoment: moment.tz('2017-05-24', 'UTC')}]],
],
loading: {
futureNextUrl: null,
pastNextUrl: null,
allOpportunitiesLoaded: true,
},
userId: '1',
opportunities: {
items: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone', dismissed: false},
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone', dismissed: false },
{ id: 3, firstName: 'Tommy', lastName: 'Flintstone', dismissed: false },
{ id: 4, firstName: 'Bill', lastName: 'Flintstone', dismissed: false },
{ id: 5, firstName: 'George', lastName: 'Flintstone', dismissed: false },
{ id: 6, firstName: 'Randel', lastName: 'Flintstone', dismissed: false },
{ id: 7, firstName: 'Harry', lastName: 'Flintstone', dismissed: false },
{ id: 8, firstName: 'Tim', lastName: 'Flintstone', dismissed: false },
{ id: 9, firstName: 'Sara', lastName: 'Flintstone', dismissed: false }
],
nextUrl: null
}
});
describe('api actions', () => {
beforeEach(() => {
moxios.install();
expect.hasAssertions();
alertInitialize({
visualSuccessCallback () {},
visualErrorCallback () {},
srAlertCallback () {}
});
});
afterEach(() => {
moxios.uninstall();
});
describe('getNextOpportunities', () => {
it('if no more pages dispatches addOpportunities with items and null url', () => {
const mockDispatch = jest.fn();
var state = getBasicState();
state.opportunities.nextUrl = '/';
const getState = () => {
return state;
};
Actions.getNextOpportunities()(mockDispatch, getState);
expect(mockDispatch).toHaveBeenCalledWith({type: 'START_LOADING_OPPORTUNITIES'});
return moxiosWait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
headers: {
link: `</>; rel="current"`
},
response: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone', dismissed: false},
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone', dismissed: false }
]
}).then(() => {
expect(mockDispatch).toHaveBeenCalledWith({type: 'ADD_OPPORTUNITIES', payload: {
items: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone', dismissed: false},
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone', dismissed: false }
], nextUrl : null
}});
});
});
});
it('if nextUrl not set show all opportunities loaded', () => {
const mockDispatch = jest.fn();
var state = getBasicState();
state.opportunities.nextUrl = null;
const getState = () => {
return state;
};
Actions.getNextOpportunities()(mockDispatch, getState);
expect(mockDispatch).toHaveBeenCalledWith({type: 'START_LOADING_OPPORTUNITIES'});
expect(mockDispatch).toHaveBeenCalledWith({type: 'ALL_OPPORTUNITIES_LOADED'});
});
});
describe('getOpportunities', () => {
it('dispatches startLoading and initialOpportunities actions', () => {
const mockDispatch = jest.fn();
Actions.getInitialOpportunities()(mockDispatch, getBasicState);
expect(mockDispatch).toHaveBeenCalledWith({type: 'START_LOADING_OPPORTUNITIES'});
return moxiosWait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
headers: {
link: `</>; rel="next"`
},
response: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone', dismissed: false},
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone', dismissed: false }
]
}).then(() => {
expect(mockDispatch).toHaveBeenCalledWith({type: 'ADD_OPPORTUNITIES', payload: {
items: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone', dismissed: false},
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone', dismissed: false }
], nextUrl : '/'
}});
});
});
});
it('dispatches startDismissingOpportunity and dismissedOpportunity actions', () => {
const mockDispatch = jest.fn();
const plannerOverride = {
id: '10',
plannable_type: 'assignment',
dismissed: true
};
Actions.dismissOpportunity("6", plannerOverride)(mockDispatch, getBasicState);
expect(mockDispatch).toHaveBeenCalledWith({"payload": "6", "type": "START_DISMISSING_OPPORTUNITY"});
return moxiosWait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 201,
response: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone' },
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone' }
]
}).then(() => {
expect(mockDispatch).toHaveBeenCalledWith({type: 'DISMISSED_OPPORTUNITY', payload: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone' },
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone' }
]});
});
});
});
it('dispatches startDismissingOpportunity and dismissedOpportunity actions when given override', () => {
const mockDispatch = jest.fn();
Actions.dismissOpportunity("6", {id: "6"})(mockDispatch, getBasicState);
expect(mockDispatch).toHaveBeenCalledWith({"payload": "6", "type": "START_DISMISSING_OPPORTUNITY"});
return moxiosWait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 201,
response: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone' },
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone' }
]
}).then(() => {
expect(mockDispatch).toHaveBeenCalledWith({type: 'DISMISSED_OPPORTUNITY', payload: [
{ id: 1, firstName: 'Fred', lastName: 'Flintstone' },
{ id: 2, firstName: 'Wilma', lastName: 'Flintstone' }
]});
});
});
});
it('makes correct request for dismissedOpportunity for existing override', () => {
const plannerOverride = {
id: '10',
plannable_type: 'assignment',
dismissed: true
};
Actions.dismissOpportunity('6', plannerOverride)(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('put');
expect(request.url).toBe('api/v1/planner/overrides/10');
expect(JSON.parse(request.config.data)).toMatchObject(plannerOverride);
});
});
it('makes correct request for dismissedOpportunity for new override', () => {
const plannerOverride = {
plannable_id: '10',
dismissed: true,
plannable_type: 'assignment'
};
Actions.dismissOpportunity('10', plannerOverride)(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner/overrides');
expect(JSON.parse(request.config.data)).toMatchObject(plannerOverride);
});
});
it('calls the alert function when a failure occurs', (done) => {
const mockDispatch = jest.fn();
const fakeAlert = jest.fn();
alertInitialize({
visualErrorCallback: fakeAlert
});
Actions.getInitialOpportunities()(mockDispatch, getBasicState);
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
}).then(() => {
expect(fakeAlert).toHaveBeenCalled();
done();
});
});
});
});
describe('savePlannerItem', () => {
it('dispatches saving and saved actions', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data'};
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(savePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVING_PLANNER_ITEM', payload: {item: plannerItem, isNewItem: true}});
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVED_PLANNER_ITEM', payload: savePromise});
});
it('sets isNewItem to false if the item id exists', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data', id: '42'};
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(savePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVING_PLANNER_ITEM', payload: {item: plannerItem, isNewItem: false}});
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVED_PLANNER_ITEM', payload: savePromise});
});
it('sends transformed data in the request', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data'};
Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosWait(request => {
expect(JSON.parse(request.config.data)).toMatchObject({some: 'data', transformedToApi: true});
});
});
it('resolves the promise with transformed response data', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data'};
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
savePromise
).then((result) => {
expect(result).toMatchObject({
item: {some: 'response data', transformedToInternal: true},
isNewItem: true,
});
});
});
it('does a post if the planner item is new (no id)', () => {
const plannerItem = {some: 'data'};
Actions.savePlannerItem(plannerItem)(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner_notes');
expect(JSON.parse(request.config.data)).toMatchObject({some: 'data', transformedToApi: true});
});
});
it('does set default time of 11:59 pm for planner date', () => {
const plannerItem = {date: moment('2017-06-22T10:05:54').tz("Atlantic/Azores").format()};
Actions.savePlannerItem(plannerItem)(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner_notes');
expect(JSON.parse(request.config.data).transformedToApi).toBeTruthy();
expect(moment(JSON.parse(request.config.data).date).tz("Atlantic/Azores").format()).toBe(moment('2017-06-22T23:59:59').tz("Atlantic/Azores").format());
});
});
it('does a put if the planner item exists (has id)', () => {
const plannerItem = {id: '42', some: 'data'};
Actions.savePlannerItem(plannerItem, )(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('put');
expect(request.url).toBe('api/v1/planner_notes/42');
expect(JSON.parse(request.config.data)).toMatchObject({id: '42', some: 'data', transformedToApi: true});
});
});
it('calls the alert function when a failure occurs', () => {
const fakeAlert = jest.fn();
const mockDispatch = jest.fn();
alertInitialize({
visualErrorCallback: fakeAlert
});
const plannerItem = {some: 'data'};
const savePromise = Actions.savePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
savePromise,
{ status: 500 }
).then((result) => {
expect(fakeAlert).toHaveBeenCalled();
});
});
});
describe('deletePlannerItem', () => {
it('dispatches deleting and deleted actions', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data'};
const deletePromise = Actions.deletePlannerItem(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(deletePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'DELETING_PLANNER_ITEM', payload: plannerItem});
expect(mockDispatch).toHaveBeenCalledWith({type: 'DELETED_PLANNER_ITEM', payload: deletePromise});
});
it('sends a delete request for the item id', () => {
const plannerItem = {id: '42', some: 'data'};
Actions.deletePlannerItem(plannerItem, )(() => {});
return moxiosWait((request) => {
expect(request.config.method).toBe('delete');
expect(request.url).toBe('api/v1/planner_notes/42');
});
});
it('resolves the promise with transformed response data', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data'};
const deletePromise = Actions.deletePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
deletePromise
).then((result) => {
expect(result).toMatchObject({some: 'response data', transformedToInternal: true});
});
});
it('calls the alert function when a failure occurs', () => {
const fakeAlert = jest.fn();
const mockDispatch = jest.fn();
alertInitialize({
visualErrorCallback: fakeAlert
});
const plannerItem = { some: 'data' };
const deletePromise = Actions.deletePlannerItem(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
deletePromise,
{ status: 500 }
).then((result) => {
expect(fakeAlert).toHaveBeenCalled();
});
});
});
describe('togglePlannerItemCompletion', () => {
it('dispatches saving and saved actions', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data'};
const savingItem = {...plannerItem, show: true, toggleAPIPending: true};
const savePromise = Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
expect(isPromise(savePromise)).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVED_PLANNER_ITEM', payload: {item: savingItem, isNewItem: false}});
expect(mockDispatch).toHaveBeenCalledWith({type: 'SAVED_PLANNER_ITEM', payload: savePromise});
});
it ('updates marked_complete and sends override data in the request', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data', marked_complete: null};
Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosWait(request => {
expect(JSON.parse(request.config.data)).toMatchObject({marked_complete: true, transformedToApiOverride: true});
});
});
it('does a post if the planner override is new (no id)', () => {
const mockDispatch = jest.fn();
const plannerItem = {id: '42', some: 'data'};
Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosWait((request) => {
expect(request.config.method).toBe('post');
expect(request.url).toBe('api/v1/planner/overrides');
expect(JSON.parse(request.config.data)).toMatchObject({marked_complete: true, transformedToApiOverride: true});
});
});
it('does a put if the planner override exists (has id)', () => {
const mockDispatch = jest.fn();
const plannerItem = {id: '42', some: 'data', planner_override: {id: '5', marked_complete: true}};
Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosWait((request) => {
expect(request.config.method).toBe('put');
expect(request.url).toBe('api/v1/planner/overrides/5');
expect(JSON.parse(request.config.data)).toMatchObject({id: '5', marked_complete: true, transformedToApiOverride: true});
});
});
it ('resolves the promise with override response data in the item', () => {
const mockDispatch = jest.fn();
const plannerItem = {some: 'data', planner_override: {id: 'override_id', marked_complete: true}};
const togglePromise = Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{some: 'response data', id: 'override_id', marked_complete: false },
togglePromise
).then((result) => {
expect(result).toMatchObject({
item: {
...plannerItem,
completed: false,
overrideId: 'override_id',
show: true,
},
});
});
});
it('calls the alert function and resends previous override when a failure occurs', () => {
const fakeAlert = jest.fn();
const mockDispatch = jest.fn();
alertInitialize({
visualErrorCallback: fakeAlert
});
const plannerItem = {some: 'data', planner_override: {id: 'override_id', marked_complete: false}};
const togglePromise = Actions.togglePlannerItemCompletion(plannerItem)(mockDispatch, getBasicState);
return moxiosRespond(
{some: 'response data'},
togglePromise,
{ status: 500 }
).then((result) => {
expect(fakeAlert).toHaveBeenCalled();
expect(result).toMatchObject({
item: { ...plannerItem}
});
});
});
});
});

View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import * as Actions from '../loading-actions';
import moxios from 'moxios';
import moment from 'moment-timezone';
import { moxiosWait, moxiosRespond} from '../../test-utils';
import { initialize as alertInitialize } from '../../utilities/alertUtils';
jest.mock('../../utilities/apiUtils', () => ({
transformApiToInternalItem: jest.fn(response => ({
...response,
newActivity: response.new_activity,
transformedToInternal: true
})),
transformInternalToApiItem: jest.fn(internal => ({...internal, transformedToApi: true})),
}));
const getBasicState = () => ({
courses: [],
groups: [],
timeZone: 'UTC',
days: [
['2017-05-22', [{id: '42', dateBucketMoment: moment.tz('2017-05-22', 'UTC')}]],
['2017-05-24', [{id: '42', dateBucketMoment: moment.tz('2017-05-24', 'UTC')}]],
],
loading: {
somePastItemsLoaded: false,
futureNextUrl: null,
pastNextUrl: null,
},
pendingItems: {
past: [],
future: [],
},
});
describe('api actions', () => {
beforeEach(() => {
moxios.install();
expect.hasAssertions();
alertInitialize({
visualSuccessCallback () {},
visualErrorCallback () {},
srAlertCallback () {}
});
});
afterEach(() => {
moxios.uninstall();
});
describe('sendFetchRequest', () => {
it('fetches from the specified moment if there is no next url in the loadingOptions', () => {
const fromMoment = moment.tz('Asia/Tokyo');
Actions.sendFetchRequest({
fromMoment, getState: () => ({loading: {}}),
});
return moxiosWait(request => {
expect(request.config.url).toBe('/api/v1/planner/items');
expect(request.config.params.start_date).toBe(fromMoment.format());
});
});
it('fetches using futureNextUrl if specified', () => {
const fromMoment = moment.tz('Asia/Tokyo');
Actions.sendFetchRequest({
fromMoment, getState: () => ({loading: {futureNextUrl: 'next url'}}),
});
return moxiosWait(request => {
expect(request.config.url).toBe('next url');
});
});
it('sends past parameters if intoThePast is specified', () => {
const fromMoment = moment.tz('Asia/Tokyo');
Actions.sendFetchRequest({
fromMoment, intoThePast: true, getState: () => ({loading: {}}),
});
return moxiosWait(request => {
expect(request.config.url).toBe('/api/v1/planner/items');
expect(request.config.params.end_date).toBe(fromMoment.format());
expect(request.config.params.order).toBe('desc');
});
});
it('sends pastNextUrl if intoThePast is specified', () => {
const fromMoment = moment.tz('Asia/Tokyo');
Actions.sendFetchRequest({
fromMoment, intoThePast: true, getState: () => ({loading: {pastNextUrl: 'past next url'}}),
});
return moxiosWait(request => {
expect(request.config.url).toBe('past next url');
});
});
it('transforms the results', () => {
const fromMoment = moment.tz('Asia/Tokyo');
const fetchPromise = Actions.sendFetchRequest({fromMoment, getState: () => ({loading: {}})});
return moxiosRespond([{some: 'items'}], fetchPromise).then(result => {
expect(result).toEqual({
response: expect.anything(),
transformedItems: [{some: 'items', transformedToInternal: true}]});
});
});
});
describe('getPlannerItems', () => {
it('dispatches START_LOADING_ITEMS, getFirstNewActivityDate, and starts the saga', () => {
const mockDispatch = jest.fn();
Actions.getPlannerItems(moment('2017-12-18'))(mockDispatch, getBasicState);
expect(mockDispatch).toHaveBeenCalledWith(Actions.startLoadingItems());
expect(mockDispatch).toHaveBeenCalledWith(Actions.startLoadingFutureSaga());
expect(typeof mockDispatch.mock.calls[1][0]).toBe('function');
const getFirstNewActivityDateThunk = mockDispatch.mock.calls[1][0];
const mockMoment = moment();
const newActivityPromise = getFirstNewActivityDateThunk(mockDispatch, getBasicState);
return moxiosRespond([{dateBucketMoment: mockMoment}], newActivityPromise).then((result) => {
expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining({
type: 'FOUND_FIRST_NEW_ACTIVITY_DATE',
}));
});
});
});
describe('getFirstNewActivityDate', () => {
it('sends deep past, filter, and order parameters', () => {
const mockDispatch = jest.fn();
const mockMoment = moment.tz('Asia/Tokyo').startOf('day');
Actions.getFirstNewActivityDate(mockMoment)(mockDispatch, getBasicState);
return moxiosWait(request => {
expect(request.config.params.filter).toBe('new_activity');
expect(request.config.params.start_date).toBe(mockMoment.subtract(6, 'months').format());
expect(request.config.params.order).toBe('asc');
});
});
it('calls the alert method when it fails to get new activity', () => {
const fakeAlert = jest.fn();
alertInitialize({
visualErrorCallback: fakeAlert
});
const mockDispatch = jest.fn();
const mockMoment = moment.tz('Asia/Tokyo').startOf('day');
const promise = Actions.getFirstNewActivityDate(mockMoment)(mockDispatch, getBasicState);
return moxiosRespond(
{ some: 'response data' },
promise,
{ status: 500 }
).then((result) => {
expect(fakeAlert).toHaveBeenCalled();
});
});
});
describe('loadFutureItems', () => {
it('dispatches GETTING_FUTURE_ITEMS and starts the saga', () => {
const mockDispatch = jest.fn();
Actions.loadFutureItems()(mockDispatch, getBasicState);
expect(mockDispatch).toHaveBeenCalledWith(Actions.gettingFutureItems());
expect(mockDispatch).toHaveBeenCalledWith(Actions.startLoadingFutureSaga());
});
it('dispatches nothing if allFutureItemsLoaded', () => {
const mockDispatch = jest.fn();
const state = getBasicState();
state.loading.allFutureItemsLoaded = true;
Actions.loadFutureItems()(mockDispatch, () => state);
expect(mockDispatch).not.toHaveBeenCalled();
});
});
describe('scrollIntoPast', () => {
it('dispatches GETTING_PAST_ITEMS and starts the saga', () => {
const mockDispatch = jest.fn();
Actions.scrollIntoPast()(mockDispatch, getBasicState);
expect(mockDispatch).toHaveBeenCalledWith(Actions.gettingPastItems());
expect(mockDispatch).toHaveBeenCalledWith(Actions.startLoadingPastSaga());
});
it('dispatches nothing if allPastItemsLoaded', () => {
const mockDispatch = jest.fn();
const state = getBasicState();
state.loading.allPastItemsLoaded = true;
Actions.scrollIntoPast()(mockDispatch, () => state);
expect(mockDispatch).not.toHaveBeenCalled();
});
});
describe('loadPastUntilNewActivity', () => {
it('dispatches getting past items and starts the saga', () => {
const mockDispatch = jest.fn();
Actions.loadPastUntilNewActivity()(mockDispatch, (getBasicState));
expect(mockDispatch).toHaveBeenCalledWith(Actions.gettingPastItems({
seekingNewActivity: true,
somePastItemsLoaded: false,
}));
expect(mockDispatch).toHaveBeenCalledWith(Actions.startLoadingPastUntilNewActivitySaga());
});
});
});

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {mergeFutureItems, mergePastItems, mergePastItemsForNewActivity}
from '../saga-actions';
import {gotPartialFutureDays, gotPartialPastDays, gotDaysSuccess} from '../loading-actions';
import {itemsToDays} from '../../utilities/daysUtils';
function getStateFn (opts = {loading: {}}) {
return () => ({
loading: {
allFutureItemsLoaded: false,
partialFutureDays: [],
allPastItemsLoaded: false,
partialPastDays: [],
...opts.loading,
},
});
}
function mockItem (date = '2017-12-18', opts = {}) {
return {
dateBucketMoment: date,
newActivity: false,
...opts,
};
}
describe('mergeFutureItems', () => {
it('extracts and dispatches complete days and returns true', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem('2017-12-18'), mockItem('2017-12-18'), mockItem('2017-12-19')];
const mockDays = itemsToDays(mockItems);
const result = mergeFutureItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialFutureDays: mockDays}}),
);
expect(result).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialFutureDays(mockDays, 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess(itemsToDays([mockItems[0], mockItems[1]]), 'mock response'));
});
it('does not dispatch gotDaysSuccess if there are no complete days and returns false', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem(), mockItem()];
const result = mergeFutureItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialFutureDays: itemsToDays(mockItems)}}),
);
expect(result).toBe(false);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialFutureDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledTimes(1);
});
it('extracts all days when allFutureItemsLoaded', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem(), mockItem()];
const result = mergeFutureItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialFutureDays: itemsToDays(mockItems), allFutureItemsLoaded: true}})
);
expect(result).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialFutureDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess(itemsToDays(mockItems), 'mock response'));
});
it('returns true when allFutureItemsLoaded but there are no available days', () => {
const mockDispatch = jest.fn();
const mockItems = [];
const result = mergeFutureItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialFutureDays: [], allFutureItemsLoaded: true}})
);
expect(result).toBe(true);
// still want to pretend something was loaded so all the loading states get updated.
expect(mockDispatch).toHaveBeenCalledWith(gotPartialFutureDays([], 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess([], 'mock response'));
});
});
describe('mergePastItems', () => {
it('extracts complete days in reverse order', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem('2017-12-17'), mockItem('2017-12-18')];
const result = mergePastItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: itemsToDays(mockItems)}}),
);
expect(result).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess(itemsToDays([mockItems[1]]), 'mock response'));
});
it('extracts all days when allPastItemsLoaded', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem(), mockItem()];
const result = mergePastItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: itemsToDays(mockItems), allPastItemsLoaded: true}})
);
expect(result).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess(itemsToDays(mockItems), 'mock response'));
});
it('returns true when allPastItemsLoaded but there are no available days', () => {
const mockDispatch = jest.fn();
const mockItems = [];
const result = mergePastItems(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: [], allPastItemsLoaded: true}})
);
expect(result).toBe(true);
// still want to pretend something was loaded so all the loading states get updated.
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays([], 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess([], 'mock response'));
});
});
describe('mergePastItemsForNewActivity', () => {
it('does not merge complete days if there is no new activity in those days', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem('2017-12-17'), mockItem('2017-12-18')];
const result = mergePastItemsForNewActivity(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: itemsToDays(mockItems)}}),
);
expect(result).toBe(false);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledTimes(1);
});
it('does not merge partial days even with new activity', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem('2017-12-18', {newActivity: true})];
const result = mergePastItemsForNewActivity(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: itemsToDays(mockItems)}}),
);
expect(result).toBe(false);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledTimes(1);
});
it('merges days if allPastItemsLoaded even if no new activity', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem('2017-12-18')];
const result = mergePastItemsForNewActivity(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: itemsToDays(mockItems), allPastItemsLoaded: true}}),
);
expect(result).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess(itemsToDays(mockItems), 'mock response'));
});
it('merges complete days when they contain new activity', () => {
const mockDispatch = jest.fn();
const mockItems = [mockItem('2017-12-17'), mockItem('2017-12-18', {newActivity: true}), mockItem('2017-12-18')];
const result = mergePastItemsForNewActivity(mockItems, 'mock response')(mockDispatch,
getStateFn({loading: {partialPastDays: itemsToDays(mockItems)}}),
);
expect(result).toBe(true);
expect(mockDispatch).toHaveBeenCalledWith(gotPartialPastDays(itemsToDays(mockItems), 'mock response'));
expect(mockDispatch).toHaveBeenCalledWith(gotDaysSuccess(itemsToDays([mockItems[1], mockItems[2]]), 'mock response'));
});
});

View File

@ -0,0 +1,109 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import moment from 'moment-timezone';
import { select, call, put } from 'redux-saga/effects';
import { gotItemsError, sendFetchRequest } from '../../actions/loading-actions';
import { loadPastUntilNewActivitySaga, loadPastSaga, loadFutureSaga } from '../sagas';
import {
mergeFutureItems, mergePastItems, mergePastItemsForNewActivity
} from '../saga-actions';
function initialState (overrides = {}) {
return {loading: {seekingNewActivity: true}, days: [], timeZone: 'Asia/Tokyo', ...overrides};
}
function setupLoadingPastUntilNewActivitySaga () {
const generator = loadPastUntilNewActivitySaga();
generator.next();
generator.next(initialState());
return generator;
}
describe('loadPastUntilNewActivitySaga', () => {
it('sends a fetch request for past items', () => {
const generator = loadPastUntilNewActivitySaga();
expect(generator.next().value).toEqual(select());
const currentState = initialState();
const startOfDay = moment.tz(currentState.timeZone).startOf('day');
expect(generator.next(currentState).value).toEqual(call(sendFetchRequest, {
getState: expect.any(Function),
fromMoment: startOfDay,
intoThePast: true,
}));
});
it('an iteration calls sendFetchRequest, calls the action creator, puts the thunk, and quits', () => {
const generator = setupLoadingPastUntilNewActivitySaga();
expect(generator.next({transformedItems: 'some items', response: 'some response'}).value)
.toEqual(call(mergePastItemsForNewActivity, 'some items', 'some response'));
expect(generator.next('a thunk').value)
.toEqual(put('a thunk'));
expect(generator.next(true).done).toBeTruthy();
});
it('loops when the thunk returns false', () => {
const generator = setupLoadingPastUntilNewActivitySaga();
generator.next('fetch result');
generator.next('a thunk');
const nextIteration = generator.next(false);
expect(nextIteration.done).toBeFalsy();
expect(nextIteration.value).toEqual(select());
expect(generator.next(initialState()).value)
.toEqual(call(sendFetchRequest, expect.anything()));
});
it('aborts and reports if the fetch fails', () => {
const generator = setupLoadingPastUntilNewActivitySaga();
const expectedError = new Error('some error');
expect(generator.throw(expectedError).value).toEqual(put(gotItemsError(expectedError)));
expect(() => generator.next()).toThrow();
});
});
describe('loadPastSaga', () => {
it('uses the past methods', () => {
const generator = loadPastSaga();
generator.next();
expect(generator.next(initialState()).value).toEqual(call(sendFetchRequest, {
getState: expect.any(Function),
fromMoment: moment.tz('Asia/Tokyo').startOf('day'),
intoThePast: true,
}));
expect(generator.next({transformedItems: 'some items', response: 'response'}).value)
.toEqual(call(mergePastItems, 'some items', 'response'));
});
// not doing a full sequence of tests becuase the code is shared with the above saga
});
describe('loadFutureSaga', () => {
it('uses the future methods', () => {
const generator = loadFutureSaga();
generator.next();
expect(generator.next(initialState()).value).toEqual(call(sendFetchRequest, {
getState: expect.any(Function),
fromMoment: moment.tz('Asia/Tokyo').startOf('day'),
intoThePast: false,
}));
expect(generator.next({transformedItems: 'some items', response: 'response'}).value)
.toEqual(call(mergeFutureItems, 'some items', 'response'));
});
// not doing a full sequence of tests becuase the code is shared with the above saga
});

View File

@ -0,0 +1,224 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { createActions } from 'redux-actions';
import axios from 'axios';
import moment from 'moment';
import configureAxios from '../utilities/configureAxios';
import { alert } from '../utilities/alertUtils';
import formatMessage from '../format-message';
import parseLinkHeader from 'parse-link-header';
import {
transformInternalToApiItem,
transformInternalToApiOverride,
transformPlannerNoteApiToInternalItem
} from '../utilities/apiUtils';
configureAxios(axios);
export const {
initialOptions,
addOpportunities,
startLoadingOpportunities,
startDismissingOpportunity,
allOpportunitiesLoaded,
savingPlannerItem,
savedPlannerItem,
dismissedOpportunity,
deletingPlannerItem,
deletedPlannerItem,
updateTodo,
clearUpdateTodo,
openEditingPlannerItem,
cancelEditingPlannerItem,
} = createActions(
'INITIAL_OPTIONS',
'ADD_OPPORTUNITIES',
'START_LOADING_OPPORTUNITIES',
'START_DISMISSING_OPPORTUNITY',
'ALL_OPPORTUNITIES_LOADED',
'SAVING_PLANNER_ITEM',
'SAVED_PLANNER_ITEM',
'DISMISSED_OPPORTUNITY',
'DELETING_PLANNER_ITEM',
'DELETED_PLANNER_ITEM',
'UPDATE_TODO',
'CLEAR_UPDATE_TODO',
'OPEN_EDITING_PLANNER_ITEM',
'CANCEL_EDITING_PLANNER_ITEM',
);
export * from './loading-actions';
function saveExistingPlannerItem (apiItem) {
return axios({
method: 'put',
url: `api/v1/planner_notes/${apiItem.id}`,
data: apiItem,
});
}
function saveNewPlannerItem (apiItem) {
return axios({
method: 'post',
url: 'api/v1/planner_notes',
data: apiItem,
});
}
export const getNextOpportunities = () => {
return (dispatch, getState) => {
dispatch(startLoadingOpportunities());
if(getState().opportunities.nextUrl) {
axios({
method: 'get',
url: getState().opportunities.nextUrl,
}).then(response => {
if(parseLinkHeader(response.headers.link).next) {
dispatch(addOpportunities({items: response.data, nextUrl: parseLinkHeader(response.headers.link).next.url }));
}else {
dispatch(addOpportunities({items: response.data, nextUrl: null}));
}
}).catch(() => alert(formatMessage('Failed to load opportunities'), true));
} else {
dispatch(allOpportunitiesLoaded());
}
};
};
export const getInitialOpportunities = () => {
return (dispatch, getState) => {
dispatch(startLoadingOpportunities());
axios({
method: 'get',
url: getState().opportunities.nextUrl || '/api/v1/users/self/missing_submissions?include[]=planner_overrides',
}).then(response => {
if(parseLinkHeader(response.headers.link).next) {
dispatch(addOpportunities({items: response.data, nextUrl: parseLinkHeader(response.headers.link).next.url }));
}else {
dispatch(addOpportunities({items: response.data, nextUrl: null}));
}
}).catch(() => alert(formatMessage('Failed to load opportunities'), true));
};
};
export const dismissOpportunity = (id, plannerOverride) => {
return (dispatch, getState) => {
dispatch(startDismissingOpportunity(id));
const apiOverride = Object.assign({}, plannerOverride);
apiOverride.dismissed = true;
apiOverride.plannable_id = id;
apiOverride.plannable_type = 'assignment';
let promise = apiOverride.id ?
saveExistingPlannerOverride(apiOverride) :
saveNewPlannerOverride(apiOverride);
promise = promise.then(response => {
dispatch(dismissedOpportunity(response.data));
// TODO: When splitting into dismissed not dismissed tabs this needs to change
if(!getState().loading.allOpportunitiesLoaded && !getState().loading.loadingOpportunities && getState().opportunities.items.filter((opp) => {
return opp.planner_override && !opp.planner_override.dismissed;
}).length < 10)
dispatch(getNextOpportunities());
});
return promise;
};
};
export const savePlannerItem = (plannerItem) => {
plannerItem.date = moment(plannerItem.date).endOf('day').format('YYYY-MM-DDTHH:mm:ssZ');
return (dispatch, getState) => {
const isNewItem = !plannerItem.id;
dispatch(savingPlannerItem({item: plannerItem, isNewItem}));
const apiItem = transformInternalToApiItem(plannerItem);
let promise = isNewItem ?
saveNewPlannerItem(apiItem) :
saveExistingPlannerItem(apiItem);
promise = promise
.then(response => {
const apiItem = transformPlannerNoteApiToInternalItem(response.data, getState().courses, getState().timeZone);
return {item: apiItem, isNewItem};
})
.catch(() => alert(formatMessage('Failed to save to do'), true));
dispatch(savedPlannerItem(promise));
return promise;
};
};
export const deletePlannerItem = (plannerItem) => {
return (dispatch, getState) => {
dispatch(deletingPlannerItem(plannerItem));
const promise = axios({
method: 'delete',
url: `api/v1/planner_notes/${plannerItem.id}`,
}).then(response => transformPlannerNoteApiToInternalItem(response.data, getState().courses, getState().timeZone))
.catch(() => alert(formatMessage('Failed to delete to do'), true));
dispatch(deletedPlannerItem(promise));
return promise;
};
};
function saveExistingPlannerOverride (apiOverride) {
return axios({
method: 'put',
url: `api/v1/planner/overrides/${apiOverride.id}`,
data: apiOverride,
});
}
function saveNewPlannerOverride (apiOverride) {
return axios({
method: 'post',
url: 'api/v1/planner/overrides',
data: apiOverride,
});
}
export const togglePlannerItemCompletion = (plannerItem) => {
return (dispatch, getState) => {
const savingItem = {...plannerItem, toggleAPIPending: true, show: true};
dispatch(savedPlannerItem({item: savingItem, isNewItem: false}));
const apiOverride = transformInternalToApiOverride(plannerItem, getState().userId);
apiOverride.marked_complete = !apiOverride.marked_complete;
let promise = apiOverride.id ?
saveExistingPlannerOverride(apiOverride) :
saveNewPlannerOverride(apiOverride);
promise = promise.then(response => ({
item: updateOverrideDataOnItem(plannerItem, response.data),
isNewItem: false,
})).catch(response => {
alert(formatMessage('Unable to mark as complete.'), true);
return ({
item: plannerItem,
isNewItem: false,
});
});
dispatch(savedPlannerItem(promise));
return promise;
};
};
function updateOverrideDataOnItem (plannerItem, apiOverride) {
let updatedItem = {...plannerItem};
updatedItem.overrideId = apiOverride.id;
updatedItem.completed = apiOverride.marked_complete;
updatedItem.show = true;
return updatedItem;
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { createActions, createAction } from 'redux-actions';
import axios from 'axios';
import { transformApiToInternalItem } from '../utilities/apiUtils';
import { alert } from '../utilities/alertUtils';
import formatMessage from '../format-message';
import { itemsToDays } from '../utilities/daysUtils';
export const {
startLoadingItems,
foundFirstNewActivityDate,
gettingFutureItems,
allFutureItemsLoaded,
allPastItemsLoaded,
gotItemsError,
startLoadingPastSaga,
startLoadingFutureSaga,
startLoadingPastUntilNewActivitySaga,
} = createActions(
'START_LOADING_ITEMS',
'FOUND_FIRST_NEW_ACTIVITY_DATE',
'GETTING_FUTURE_ITEMS',
'ALL_FUTURE_ITEMS_LOADED',
'ALL_PAST_ITEMS_LOADED',
'GOT_ITEMS_ERROR',
'START_LOADING_PAST_SAGA',
'START_LOADING_FUTURE_SAGA',
'START_LOADING_PAST_UNTIL_NEW_ACTIVITY_SAGA',
);
export const gettingPastItems = createAction('GETTING_PAST_ITEMS', (opts = {seekingNewActivity: false, somePastItemsLoaded: false}) => {
return opts;
});
export const gotDaysSuccess = createAction('GOT_DAYS_SUCCESS', (newDays, response) => {
return { internalDays: newDays, response };
});
export function gotItemsSuccess (newItems, response) {
return gotDaysSuccess(itemsToDays(newItems), response);
}
export const gotPartialFutureDays = createAction('GOT_PARTIAL_FUTURE_DAYS', (newDays, response) => {
return { internalDays: newDays, response };
});
export const gotPartialPastDays = createAction('GOT_PARTIAL_PAST_DAYS', (newDays, response) => {
return { internalDays: newDays, response };
});
export function getFirstNewActivityDate (fromMoment) {
// We are requesting ascending order and only grabbing the first item,
// specifically so we know what the very oldest new activity is
return (dispatch, getState) => {
fromMoment = fromMoment.clone().subtract(6, 'months');
return axios.get('api/v1/planner/items', { params: {
start_date: fromMoment.format(),
filter: 'new_activity',
order: 'asc'
}}).then(response => {
if (response.data.length) {
const first = transformApiToInternalItem(response.data[0], getState().courses, getState().groups, getState().timeZone);
dispatch(foundFirstNewActivityDate(first.dateBucketMoment));
}
}).catch(() => alert(formatMessage('Failed to get new activity'), true));
};
}
export function getPlannerItems (fromMoment) {
return (dispatch, getState) => {
dispatch(startLoadingItems());
dispatch(getFirstNewActivityDate(fromMoment));
dispatch(startLoadingFutureSaga());
};
}
export function loadFutureItems () {
return (dispatch, getState) => {
if (getState().loading.allFutureItemsLoaded) return;
dispatch(gettingFutureItems());
dispatch(startLoadingFutureSaga());
};
}
export function scrollIntoPast () {
return (dispatch, getState) => {
if (getState().loading.allPastItemsLoaded) return;
dispatch(gettingPastItems({
seekingNewActivity: false,
somePastItemsLoaded: getState().loading.somePastItemsLoaded
}));
dispatch(startLoadingPastSaga());
};
}
export const loadPastUntilNewActivity = () => (dispatch, getState) => {
dispatch(gettingPastItems({
seekingNewActivity: true,
somePastItemsLoaded: getState().loading.somePastItemsLoaded
}));
dispatch(startLoadingPastUntilNewActivitySaga());
};
export function sendFetchRequest (loadingOptions) {
return axios.get(...fetchParams(loadingOptions))
.then(response => handleFetchResponse(loadingOptions, response))
// no .catch: it's up to the sagas to handle errors
;
}
function fetchParams (loadingOptions) {
let timeParam = 'start_date';
let linkField = 'futureNextUrl';
if (loadingOptions.intoThePast) {
timeParam = 'end_date';
linkField = 'pastNextUrl';
}
const nextPageUrl = loadingOptions.getState().loading[linkField];
if (nextPageUrl) {
return [nextPageUrl, {}];
} else {
const params = {
[timeParam]: loadingOptions.fromMoment.format()
};
if (loadingOptions.intoThePast) {
params.order = 'desc';
}
return [
'/api/v1/planner/items',
{ params },
];
}
}
function handleFetchResponse (loadingOptions, response) {
const transformedItems = transformItems(loadingOptions, response.data);
return {response, transformedItems};
}
function transformItems (loadingOptions, items) {
return items.map(item => transformApiToInternalItem(
item,
loadingOptions.getState().courses,
loadingOptions.getState().groups,
loadingOptions.getState().timeZone,
));
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import * as LA from './loading-actions.js';
import {anyNewActivityDays} from '../utilities/statusUtils';
import {itemsToDays} from '../utilities/daysUtils';
export const mergeFutureItems = (newFutureItems, response) => (dispatch, getState) => {
dispatch(LA.gotPartialFutureDays(itemsToDays(newFutureItems), response));
const state = getState();
const completeDays = extractCompleteDays(
state.loading.partialFutureDays, state.loading.allFutureItemsLoaded, 'asc',
);
return mergeCompleteDays(completeDays, dispatch, state.loading.allFutureItemsLoaded, response);
};
export const mergePastItems = (newPastItems, response) => (dispatch, getState) => {
dispatch(LA.gotPartialPastDays(itemsToDays(newPastItems), response));
const state = getState();
const completeDays = extractCompleteDays(
state.loading.partialPastDays, state.loading.allPastItemsLoaded, 'desc',
);
return mergeCompleteDays(completeDays, dispatch, state.loading.allPastItemsLoaded, response);
};
export const mergePastItemsForNewActivity = (newPastItems, response) => (dispatch, getState) => {
dispatch(LA.gotPartialPastDays(itemsToDays(newPastItems), response));
const state = getState();
const completeDays = extractCompleteDays(
state.loading.partialPastDays, state.loading.allPastItemsLoaded, 'desc',
);
if (anyNewActivityDays(completeDays) || state.loading.allPastItemsLoaded) {
return mergeCompleteDays(completeDays, dispatch, state.loading.allPastItemsLoaded, response);
}
return false;
};
function mergeCompleteDays (completeDays, dispatch, allItemsLoaded, response) {
if (completeDays.length || allItemsLoaded) {
dispatch(LA.gotDaysSuccess(completeDays, response));
return true;
}
return false;
}
function extractCompleteDays (daysArray, everythingCompleted, direction) {
const partialDays = daysArray.slice();
if (direction === 'desc') partialDays.reverse();
if (everythingCompleted) return partialDays;
const completeDays = partialDays.slice(0, -1);
return completeDays;
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { put, select, call, all, takeEvery } from 'redux-saga/effects';
import { getFirstLoadedMoment, getLastLoadedMoment } from '../utilities/dateUtils';
import {
gotItemsError, sendFetchRequest,
} from './loading-actions';
import {
mergeFutureItems, mergePastItems, mergePastItemsForNewActivity
} from './saga-actions';
export default function* allSagas () {
yield all([
call(watchForSagas),
]);
}
function* watchForSagas () {
yield takeEvery('START_LOADING_PAST_SAGA', loadPastSaga);
yield takeEvery('START_LOADING_FUTURE_SAGA', loadFutureSaga);
yield takeEvery('START_LOADING_PAST_UNTIL_NEW_ACTIVITY_SAGA', loadPastUntilNewActivitySaga);
}
// fromMomentFunction: function
// arg: currentState
// returns: the fromMoment that should be passed to the fetch request
// actionCreator: function
// arg: transformedItems - an array of new items to merge into the state
// arg: response - the response of the fetch
// returns: an action that returns a thunk.
// The thunk should return:
// true if the conditions were met and we can stop loading items
// false if the new items did not meet the conditions and we should load more items
// opts: for sendFetchRequest
// intoThePast
function* loadingLoop (fromMomentFunction, actionCreator, opts) {
try {
let currentState = null;
const getState = () => currentState; // don't want create a new function inside a loop
let continueLoading = true;
while (continueLoading) {
currentState = yield select();
const fromMoment = fromMomentFunction(currentState);
const loadingOptions = {fromMoment, getState, ...opts};
const {transformedItems, response} = yield call(sendFetchRequest, loadingOptions);
const thunk = yield call(actionCreator, transformedItems, response);
continueLoading = !(yield put(thunk));
}
} catch (e) {
yield put(gotItemsError(e));
throw e;
}
}
export function* loadPastSaga () {
yield* loadingLoop(fromMomentPast, mergePastItems, {intoThePast: true});
}
export function* loadFutureSaga () {
yield* loadingLoop(fromMomentFuture, mergeFutureItems, {intoThePast: false});
}
export function* loadPastUntilNewActivitySaga () {
yield* loadingLoop(fromMomentPast, mergePastItemsForNewActivity, {intoThePast: true});
}
function fromMomentPast (state) {
return getFirstLoadedMoment(state.days, state.timeZone);
}
function fromMomentFuture (state) {
const lastMoment = getLastLoadedMoment(state.days, state.timeZone);
if (state.days.length) lastMoment.add(1, 'days');
return lastMoment;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';
import { shallow } from 'enzyme';
import BadgeList from '../index';
import Pill from '@instructure/ui-core/lib/components/Pill';
it('renders Pill components as list items', () => {
const wrapper = shallow(
<BadgeList>
<Pill text="Pill 1" />
<Pill text="Pill 2" />
<Pill text="Pill 3" />
</BadgeList>
);
expect(wrapper).toMatchSnapshot();
});

View File

@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders Pill components as list items 1`] = `
<ul>
<li>
<Pill
text="Pill 1"
variant="default"
/>
</li>
<li>
<Pill
text="Pill 2"
variant="default"
/>
</li>
<li>
<Pill
text="Pill 3"
variant="default"
/>
</li>
</ul>
`;

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { Children, Component } from 'react';
import themeable from '@instructure/ui-themeable/lib';
import CustomPropTypes from '@instructure/ui-utils/lib/react/CustomPropTypes';
import Pill from '@instructure/ui-core/lib/components/Pill';
import styles from './styles.css';
import theme from './theme.js';
class BadgeList extends Component {
static propTypes = {
children: CustomPropTypes.Children.oneOf([Pill])
}
renderChildren () {
return Children.map(this.props.children, (child) => {
return (
<li key={child.key} className={styles.item}>
{child}
</li>
);
});
}
render () {
return (
<ul className={styles.root}>
{this.renderChildren()}
</ul>
);
}
}
export default themeable(theme, styles)(BadgeList);

View File

@ -0,0 +1,13 @@
.root {
line-height: var(--lineHeight);
list-style-type: none;
margin: 0;
padding: 0;
text-align: inherit;
}
.item {
display: inline-block;
vertical-align: middle;
margin: var(--itemMargin);
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export default function generator (typography) {
return {
lineHeight: typography.lineHeightCondensed,
itemMargin: '0.0625rem'
};
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';
import { shallow, mount } from 'enzyme';
import {CompletedItemsFacade} from '../index';
it('renders as a div with a Checkbox and a string of text indicating count', () => {
const wrapper = shallow(
<CompletedItemsFacade
onClick={() => {}}
itemCount={3}
/>
);
expect(wrapper).toMatchSnapshot();
});
it('calls the onClick prop when clicked', () => {
const fakeOnClick = jest.fn();
const wrapper = shallow(
<CompletedItemsFacade
onClick={fakeOnClick}
itemCount={0}
/>
);
wrapper.find('Checkbox').simulate('click');
expect(fakeOnClick).toHaveBeenCalled();
});
it('displays Pills when given them', () => {
const wrapper = shallow(
<CompletedItemsFacade
onClick={() => {}}
itemCount={3}
badges={[{id: 'new_graded', text: 'Graded' }]}
/>
);
expect(wrapper.find('Pill')).toHaveLength(1);
});
it('registers itself as animatable', () => {
const fakeRegister = jest.fn();
const fakeDeregister = jest.fn();
const wrapper = mount(
<CompletedItemsFacade
onClick={() => {}}
registerAnimatable={fakeRegister}
deregisterAnimatable={fakeDeregister}
animatableIndex={42}
animatableItemIds={['1', '2', '3']}
itemCount={3}
/>
);
const instance = wrapper.instance();
expect(fakeRegister).toHaveBeenCalledWith('item', instance, 42, ['1', '2', '3']);
wrapper.setProps({animatableItemIds: ['2', '3', '4']});
expect(fakeDeregister).toHaveBeenCalledWith('item', instance, ['1', '2', '3']);
expect(fakeRegister).toHaveBeenCalledWith('item', instance, 42, ['2', '3', '4']);
wrapper.unmount();
expect(fakeDeregister).toHaveBeenCalledWith('item', instance, ['2', '3', '4']);
});

View File

@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders as a div with a Checkbox and a string of text indicating count 1`] = `
<div>
<div>
<Checkbox
defaultChecked={true}
disabled={false}
inline={true}
label="Show 3 completed items"
onClick={[Function]}
size="medium"
variant="simple"
/>
</div>
<div />
</div>
`;

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { Component } from 'react';
import themeable from '@instructure/ui-themeable/lib';
import containerQuery from '@instructure/ui-utils/lib/react/containerQuery';
import Checkbox from '@instructure/ui-core/lib/components/Checkbox';
import Pill from '@instructure/ui-core/lib/components/Pill';
import BadgeList from '../BadgeList';
import { func, number, string, arrayOf, shape } from 'prop-types';
import {animatable} from '../../dynamic-ui';
import styles from './styles.css';
import theme from './theme.js';
import formatMessage from '../../format-message';
export class CompletedItemsFacade extends Component {
static propTypes = {
onClick: func.isRequired,
itemCount: number.isRequired,
badges: arrayOf(shape({
text: string,
variant: string
})),
animatableIndex: number,
animatableItemIds: arrayOf(string),
registerAnimatable: func,
deregisterAnimatable: func,
}
static defaultProps = {
badges: [],
registerAnimatable: () => {},
deregisterAnimatable: () => {},
}
componentDidMount () {
this.props.registerAnimatable('item', this, this.props.animatableIndex, this.props.animatableItemIds);
}
componentWillReceiveProps (newProps) {
this.props.deregisterAnimatable('item', this, this.props.animatableItemIds);
this.props.registerAnimatable('item', this, newProps.animatableIndex, newProps.animatableItemIds);
}
componentWillUnmount () {
this.props.deregisterAnimatable('item', this, this.props.animatableItemIds);
}
getFocusable () { return this.mainButton; }
renderBadges () {
if (this.props.badges.length) {
return (
<BadgeList>
{
this.props.badges.map((b) => (
<Pill
key={b.id}
text={b.text}
variant={b.variant} />
))
}
</BadgeList>
);
}
return null;
}
render () {
return (
<div className={styles.root}>
<div className={styles.contentPrimary}>
<Checkbox
defaultChecked
inline
label={
formatMessage(`{
count, plural,
one {Show # completed item}
other {Show # completed items}
}`, { count: this.props.itemCount })
}
onClick={this.props.onClick}
/>
</div>
<div className={styles.contentSecondary}>
{this.renderBadges()}
</div>
</div>
);
}
}
export default animatable(themeable(theme, styles)(
// we can update this to be whatever works for this component and its content
containerQuery({
'media-x-large': { minWidth: '68rem' },
'media-large': { minWidth: '58rem' },
'media-medium': { minWidth: '34rem' }
})(CompletedItemsFacade)
));

View File

@ -0,0 +1,41 @@
.root {
font-family: var(--fontFamily);
color: var(--color);
box-sizing: border-box;
padding: var(--padding);
border-bottom: var(--borderWidth) solid var(--borderColor);
}
.contentPrimary {
margin-bottom: var(--bottomMarginPhoneUp);
box-sizing: border-box;
min-width: 1px;
}
.contentSecondary {
box-sizing: border-box;
min-width: 1px;
}
[data-media-medium] {
&.root {
padding: var(--paddingMedium);
display: flex;
align-items: center;
}
.contentPrimary {
margin-bottom: 0;
flex: 1;
}
.contentSecondary {
margin-left: 1rem;
}
}
[data-media-x-large] {
&.root {
padding: var(--paddingLarge);
}
}

View File

@ -0,0 +1,35 @@
/* Global variables (colors, typography, spacing, etc.) are defined in lib/themes */
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export default function generator ({ borders, colors, media, spacing, typography }) {
return {
fontFamily: typography.fontFamily,
color: colors.licorice,
padding: `${spacing.small} ${spacing.xSmall}`,
paddingMedium: `${spacing.small}`,
paddingLarge: `${spacing.small} ${spacing.medium}`,
borderWidth: borders.widthSmall,
borderColor: colors.tiara,
bottomMarginPhoneUp: spacing.xSmall,
...media
};
}

View File

@ -0,0 +1,239 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';
import { shallow, mount } from 'enzyme';
import moment from 'moment';
import {Day} from '../index';
it('renders the base component with required props', () => {
const wrapper = shallow(
<Day timeZone="America/Denver" day="2017-04-25" alwaysRender />
);
expect(wrapper).toMatchSnapshot();
});
it('renders the friendly name in large text when it is today', () => {
const today = moment();
const wrapper = shallow(
<Day timeZone="America/Denver" day={today.format('YYYY-MM-DD')} />
);
expect(wrapper.find('Text').first().props().size).toEqual('large');
});
it('renders the friendlyName in medium text when it is not today', () => {
const yesterday = moment().add(1, 'days');
const wrapper = shallow(
<Day timeZone="America/Denver" day={yesterday.format('YYYY-MM-DD')} />
);
expect(wrapper.find('Text').first().props().size).toEqual('medium');
});
it('groups itemsForDay based on context type + context id', () => {
const items = [{
title: 'Black Friday',
context: {
type: 'Course',
id: 128,
inform_students_of_overdue_submissions: true
}
}, {
title: 'San Juan',
context: {
type: 'Course',
id: 256,
inform_students_of_overdue_submissions: true
}
}, {
title: 'Roll for the Galaxy',
context: {
type: 'Course',
id: 256,
inform_students_of_overdue_submissions: true
}
}, {
title: 'Same id, different type',
context: {
type: 'Group',
id: 256,
inform_students_of_overdue_submissions: false
}
}];
const wrapper = shallow(
<Day timeZone="America/Denver" day="2017-04-25" itemsForDay={items} />
);
const groupedItems = wrapper.state('groupedItems');
expect(groupedItems['Course128'].length).toEqual(1);
expect(groupedItems['Course256'].length).toEqual(2);
expect(groupedItems['Group256'].length).toEqual(1);
});
it('renders grouping correctly when having itemsForDay', () => {
const items = [{
title: 'Black Friday',
context: {
type: 'Course',
id: 128,
url:"http://www.non_default_url.com",
inform_students_of_overdue_submissions: true
}
}, {
title: 'San Juan',
context: {
type: 'Course',
id: 256,
url:"http://www.non_default_url.com",
inform_students_of_overdue_submissions: true
}
}, {
title: 'Roll for the Galaxy',
context: {
type: 'Course',
id: 256,
url:"http://www.non_default_url.com",
inform_students_of_overdue_submissions: true
}
}, {
title: 'Same id, different type',
context: {
type: 'Group',
id: 256,
inform_students_of_overdue_submissions: false
}
}];
const wrapper = shallow(
<Day timeZone="America/Denver" day="2017-04-25" itemsForDay={items} />
);
expect(wrapper).toMatchSnapshot();
});
it('groups itemsForDay that have no context into the "Notes" category', () => {
const items = [{
title: 'Black Friday',
context: {
type: 'Course',
id: 128,
inform_students_of_overdue_submissions: true
}
}, {
title: 'San Juan',
context: {
type: 'Course',
id: 256,
inform_students_of_overdue_submissions: true
}
}, {
title: 'Roll for the Galaxy',
context: {
type: 'Course',
id: 256,
inform_students_of_overdue_submissions: true
}
}, {
title: 'Get work done!'
}];
const wrapper = shallow(
<Day timeZone="America/Denver" day="2017-04-25" itemsForDay={items} />
);
const groupedItems = wrapper.state('groupedItems');
expect(groupedItems.Notes.length).toEqual(1);
});
it('groups itemsForDay that come in on prop changes', () => {
const items = [{
title: 'Black Friday',
context: {
type: 'Course',
id: 128,
inform_students_of_overdue_submissions: true
}
}, {
title: 'San Juan',
context: {
type: 'Course',
id: 256,
inform_students_of_overdue_submissions: true
}
}];
const wrapper = shallow(
<Day timeZone="America/Denver" day="2017-04-25" itemsForDay={items} registerAnimatable={() => {}} deregisterAnimatable={() => {}} />
);
let groupedItems = wrapper.state('groupedItems');
expect(Object.keys(groupedItems).length).toEqual(2);
const newItemsForDay = items.concat([{
title: 'Roll for the Galaxy',
context: {
type: 'Course',
id: 256,
inform_students_of_overdue_submissions: true
}
}, {
title: 'Get work done!'
}]);
wrapper.setProps({ itemsForDay: newItemsForDay });
groupedItems = wrapper.state('groupedItems');
expect(Object.keys(groupedItems).length).toEqual(3);
});
it('renders nothing when there are no items and the date is outside of two weeks', () => {
const date = moment.tz("Asia/Tokyo").add(15, 'days');
const wrapper = shallow(
<Day timeZone="Asia/Tokyo" day={date.format('YYYY-MM-DD')} itemsForDay={[]} />
);
expect(wrapper.type()).toBeNull();
});
it('renders when there are no items but within two weeks', () => {
const date = moment.tz("Asia/Tokyo").add(13, 'days');
const wrapper = shallow(
<Day timeZone="Asia/Tokyo" day={date.format('YYYY-MM-DD')} itemsForDay={[]} />
);
expect(wrapper.type).not.toBeNull();
});
it('registers itself as animatable', () => {
const fakeRegister = jest.fn();
const fakeDeregister = jest.fn();
const firstItems = [{title: 'asdf', context: {id: 128, inform_students_of_overdue_submissions: true}, id: '1', uniqueId: 'first'}, {title: 'jkl', context: {id: 256, inform_students_of_overdue_submissions: true}, id: '2', uniqueId: 'second'}];
const secondItems = [{title: 'qwer', context: {id: 128, inform_students_of_overdue_submissions: true}, id: '3', uniqueId: 'third'}, {title: 'uiop', context: {id: 256, inform_students_of_overdue_submissions: true}, id: '4', uniqueId: 'fourth'}];
const wrapper = mount(
<Day
day={'2017-08-11'}
timeZone="Asia/Tokyo"
animatableIndex={42}
itemsForDay={firstItems}
registerAnimatable={fakeRegister}
deregisterAnimatable={fakeDeregister}
updateTodo={() => {}}
/>
);
const instance = wrapper.instance();
expect(fakeRegister).toHaveBeenCalledWith('day', instance, 42, ['first', 'second']);
wrapper.setProps({itemsForDay: secondItems});
expect(fakeDeregister).toHaveBeenCalledWith('day', instance, ['first', 'second']);
expect(fakeRegister).toHaveBeenCalledWith('day', instance, 42, ['third', 'fourth']);
wrapper.unmount();
expect(fakeDeregister).toHaveBeenCalledWith('day', instance, ['third', 'fourth']);
});

View File

@ -0,0 +1,146 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders grouping correctly when having itemsForDay 1`] = `
<div>
<Heading
border="none"
color="inherit"
ellipsis={false}
level="h2"
>
<Text
as="div"
letterSpacing="normal"
lineHeight="condensed"
size="medium"
transform="uppercase"
>
Tuesday
</Text>
<Text
as="div"
letterSpacing="normal"
lineHeight="condensed"
size="medium"
>
April 25, 2017
</Text>
</Heading>
<div>
<Animatable(Grouping)
animatableIndex={0}
items={
Array [
Object {
"context": Object {
"id": 128,
"inform_students_of_overdue_submissions": true,
"type": "Course",
"url": "http://www.non_default_url.com",
},
"title": "Black Friday",
},
]
}
theme={
Object {
"titleColor": null,
}
}
timeZone="America/Denver"
url="http://www.non_default_url.com"
/>
<Animatable(Grouping)
animatableIndex={1}
items={
Array [
Object {
"context": Object {
"id": 256,
"inform_students_of_overdue_submissions": true,
"type": "Course",
"url": "http://www.non_default_url.com",
},
"title": "San Juan",
},
Object {
"context": Object {
"id": 256,
"inform_students_of_overdue_submissions": true,
"type": "Course",
"url": "http://www.non_default_url.com",
},
"title": "Roll for the Galaxy",
},
]
}
theme={
Object {
"titleColor": null,
}
}
timeZone="America/Denver"
url="http://www.non_default_url.com"
/>
<Animatable(Grouping)
animatableIndex={2}
items={
Array [
Object {
"context": Object {
"id": 256,
"inform_students_of_overdue_submissions": false,
"type": "Group",
},
"title": "Same id, different type",
},
]
}
theme={
Object {
"titleColor": null,
}
}
timeZone="America/Denver"
/>
</div>
</div>
`;
exports[`renders the base component with required props 1`] = `
<div>
<Heading
border="bottom"
color="inherit"
ellipsis={false}
level="h2"
>
<Text
as="div"
letterSpacing="normal"
lineHeight="condensed"
size="medium"
transform="uppercase"
>
Tuesday
</Text>
<Text
as="div"
letterSpacing="normal"
lineHeight="condensed"
size="medium"
>
April 25, 2017
</Text>
</Heading>
<div>
<Container
display="block"
margin="small 0 0 0"
textAlign="center"
>
No "To-Do's" for this day yet.
</Container>
</div>
</div>
`;

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that they will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { Component } from 'react';
import moment from 'moment-timezone';
import themeable from '@instructure/ui-themeable/lib';
import Heading from '@instructure/ui-core/lib/components/Heading';
import Text from '@instructure/ui-core/lib/components/Text';
import Container from '@instructure/ui-core/lib/components/Container';
import { shape, string, number, arrayOf, func, bool } from 'prop-types';
import styles from './styles.css';
import theme from './theme.js';
import { getFriendlyDate, getFullDate, isToday } from '../../utilities/dateUtils';
import { groupBy } from 'lodash';
import Grouping from '../Grouping';
import formatMessage from '../../format-message';
import { animatable } from '../../dynamic-ui';
export class Day extends Component {
static propTypes = {
day: string.isRequired,
itemsForDay: arrayOf(shape({
context: shape({
inform_students_of_overdue_submissions: bool.isRequired
})
})),
animatableIndex: number,
timeZone: string.isRequired,
toggleCompletion: func,
updateTodo: func,
alwaysRender: bool,
registerAnimatable: func,
deregisterAnimatable: func,
}
constructor (props) {
super(props);
const tzMomentizedDate = moment.tz(props.day, props.timeZone);
this.friendlyName = getFriendlyDate(tzMomentizedDate);
this.fullDate = getFullDate(tzMomentizedDate);
this.state = {
groupedItems: this.groupItems(props.itemsForDay)
};
}
componentDidMount () {
this.props.registerAnimatable('day', this, this.props.animatableIndex, this.itemUniqueIds());
}
componentWillReceiveProps (nextProps) {
this.props.deregisterAnimatable('day', this, this.itemUniqueIds());
this.props.registerAnimatable('day', this, nextProps.animatableIndex, this.itemUniqueIds(nextProps));
this.setState((state) => {
return {
groupedItems: this.groupItems(nextProps.itemsForDay)
};
});
}
componentWillUnmount () {
this.props.deregisterAnimatable('day', this, this.itemUniqueIds());
}
itemUniqueIds (props = this.props) { return props.itemsForDay.map(item => item.uniqueId); }
groupItems = (items) => groupBy(items, item => (item.context && (item.context.type+item.context.id)) || 'Notes');
hasItems () {
return !!Object.keys(this.state.groupedItems).length;
}
shouldRender () {
if (this.props.alwaysRender) return true;
const myDate = moment.tz(this.props.day, this.props.timeZone);
const today = moment.tz(this.props.timeZone);
const future = today.clone().add(2, 'weeks');
const past = today.clone().subtract(2, 'weeks');
if (myDate.isBetween(past, future, 'days')) return true;
return this.hasItems();
}
render () {
if (!this.shouldRender()) return null;
return (
<div className={styles.root} >
<Heading
border={(this.hasItems()) ? 'none' : 'bottom'}
>
<Text
as="div"
transform="uppercase"
lineHeight="condensed"
size={isToday(this.props.day) ? 'large' : 'medium'}
>
{this.friendlyName}
</Text>
<Text
as="div"
lineHeight="condensed"
>
{this.fullDate}
</Text>
</Heading>
<div>
{
(this.hasItems()) ? (
Object.keys(this.state.groupedItems).map((cid, groupIndex) => {
const groupItems = this.state.groupedItems[cid];
const courseInfo = groupItems[0].context || {};
return (
<Grouping
title={courseInfo.title}
image_url={courseInfo.image_url}
color={courseInfo.color}
timeZone={this.props.timeZone}
updateTodo={this.props.updateTodo}
items={groupItems}
animatableIndex={groupIndex}
url={courseInfo.url}
key={cid}
theme={{
titleColor: courseInfo.color || null
}}
toggleCompletion={this.props.toggleCompletion}
/>
);
})
) : (
<Container
textAlign="center"
display="block"
margin="small 0 0 0"
>
{formatMessage('No "To-Do\'s" for this day yet.')}
</Container>
)
}
</div>
</div>
);
}
}
export default animatable(themeable(theme, styles)(Day));

View File

@ -0,0 +1,13 @@
/* Variables are defined in ./theme.js */
.root {
font-size: var(--fontSize);
font-family: var(--fontFamily);
font-weight: var(--fontWeight);
line-height: var(--lineHeight);
color: var(--color);
background: var(--background);
margin-top: var(--marginTop);
}

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