diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a9d11c5..ebc6c09 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Create a report to help me improve c4l +about: Create a report to help me improve elements title: '' labels: '' assignees: '' diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml index 66a9460..ba8e2c9 100644 --- a/.github/workflows/moodle-ci.yml +++ b/.github/workflows/moodle-ci.yml @@ -1,17 +1,26 @@ -name: Moodle Plugin CI - on: [push, pull_request] jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 + mariadb: - image: mariadb:10.5 + image: mariadb:10 env: MYSQL_USER: 'root' MYSQL_ALLOW_EMPTY_PASSWORD: "true" + MYSQL_CHARACTER_SET_SERVER: "utf8mb4" + MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 @@ -19,13 +28,28 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0'] - moodle-branch: ['MOODLE_401_STABLE'] - database: [mariadb] - + include: + - php: '8.3' + moodle-branch: 'main' + database: 'mariadb' + - php: '8.3' + moodle-branch: 'main' + database: 'pgsql' + - php: '8.3' + moodle-branch: 'MOODLE_405_STABLE' + database: 'mariadb' + - php: '8.3' + moodle-branch: 'MOODLE_405_STABLE' + database: 'pgsql' + - php: '8.1' + moodle-branch: 'MOODLE_404_STABLE' + database: 'mariadb' + - php: '8.1' + moodle-branch: 'MOODLE_404_STABLE' + database: 'pgsql' steps: - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: plugin @@ -35,65 +59,60 @@ jobs: php-version: ${{ matrix.php }} extensions: ${{ matrix.extensions }} ini-values: max_input_vars=5000 + # If you are not using code coverage, keep "none". Otherwise, use "pcov" (Moodle 3.10 and up) or "xdebug". + # If you try to use code coverage with "none", it will fallback to phpdbg (which has known problems). coverage: none - name: Initialise moodle-plugin-ci run: | - composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3 + composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4 echo $(cd ci/bin; pwd) >> $GITHUB_PATH echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH sudo locale-gen en_AU.UTF-8 echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV - name: Install moodle-plugin-ci - run: | - moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 + run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 env: DB: ${{ matrix.database }} MOODLE_BRANCH: ${{ matrix.moodle-branch }} - name: PHP Lint - if: ${{ always() }} + if: ${{ !cancelled() }} run: moodle-plugin-ci phplint - - name: PHP Copy/Paste Detector - continue-on-error: true # This step will show errors but will not fail - if: ${{ always() }} - run: moodle-plugin-ci phpcpd - - - name: PHP Mess Detector - continue-on-error: true # This step will show errors but will not fail - if: ${{ always() }} - run: moodle-plugin-ci phpmd - - name: Moodle Code Checker - if: ${{ always() }} - run: moodle-plugin-ci codechecker --max-warnings 0 + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpcs --max-warnings 0 - name: Moodle PHPDoc Checker - if: ${{ always() }} - run: moodle-plugin-ci phpdoc + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpdoc --max-warnings 0 - name: Validating - if: ${{ always() }} + if: ${{ !cancelled() }} run: moodle-plugin-ci validate - name: Check upgrade savepoints - if: ${{ always() }} + if: ${{ !cancelled() }} run: moodle-plugin-ci savepoints - name: Mustache Lint - if: ${{ always() }} + if: ${{ !cancelled() }} run: moodle-plugin-ci mustache - - name: Grunt - if: ${{ matrix.moodle-branch == 'master' }} - run: moodle-plugin-ci grunt + - name: Grunt AMD + if: ${{ !cancelled() }} + run: moodle-plugin-ci grunt --tasks amd --max-lint-warnings 0 + + - name: Grunt Gherkin + if: ${{ !cancelled() }} + run: moodle-plugin-ci grunt --tasks gherkinlint --max-lint-warnings 0 - name: PHPUnit tests - if: ${{ always() }} - run: moodle-plugin-ci phpunit + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpunit --fail-on-warning - - name: Behat features - if: ${{ always() }} - run: moodle-plugin-ci behat --profile chrome + - name: Mark cancelled jobs as failed. + if: ${{ cancelled() }} + run: exit 1 diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 20d40b6..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is 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. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - 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. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - 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 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. Use with the GNU Affero General Public License. - - 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 Affero 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 special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 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 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 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. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - 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 GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. \ No newline at end of file diff --git a/README.md b/README.md index 85078c4..340849e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,133 @@ -# Components For Learning (c4l) - -A plugin for the Moodle TinyMCE editor providing a set of visual components designed explicitly for Learning, based on the parent project componentsforlearning.org. +# Elements + +## About the plugin + +A plugin for the Moodle TinyMCE editor providing customizable components for structuring learning content. It contains sets of visual components designed explicitly for learning +* from componentsforlearning.org (by Roger Segú) and +* teaching avatars, work modes, nature and culture by Dunja Speckner. No configuration settings are required for this plugin. Just install it from the Site Administration area (Plugins → Install plugins → Install plugin from ZIP file). -Once the plugin is installed, a button and a menu item will be visible in the TinyMCE editor. There is one setting to preview the components when hover the mouse cursor over each component, it is enabled by default, to change it modify enablepreview setting on Site Administration area → Plugins → Text Editors → TinyMCE editor → Components for Learning (C4L). +Once the plugin is installed, a button and a menu item will be visible in the TinyMCE editor. There is one setting to preview the components when hover the mouse cursor over each component, it is enabled by default, to change it modify enablepreview setting on Site Administration area → Plugins → Text Editors → TinyMCE editor → Elements. -The capability 'tiny/c4l:viewplugin' allows to configure the plugin visibility to any role. +The capability 'tiny/elements:viewplugin' allows to configure the plugin visibility to any role. -This plugin is based on another plugin for Atto editor named Components for Learning (C4L) (https://moodle.org/plugins/atto_c4l) that it is part of a broader, collaborative project called Components for Learning (https://componentsforlearning.org). You will find there all the related documentation and detailed usage recommendations and examples for all components included here. +This plugin is a fork of tiny_c4l https://moodle.org/plugins/tiny_c4l) by Marc Català and Roger Segú and also based on their work on atto_c4l (https://moodle.org/plugins/atto_c4l). These are part of a broader, collaborative project called Components for Learning (https://componentsforlearning.org). You will find there all the related documentation and detailed usage recommendations and examples for the C4L set of components included here. Icons authored by Roger Segú, except for the following, licensed under Creative Commons CCBY, [Glasses](https://thenounproject.com/icon/70907/) by Austin Condiff, [Estimate](https://thenounproject.com/icon/1061038/) by xwoodhillx, [Quote](https://thenounproject.com/icon/77920/) by Rohith M S, [Pin](https://thenounproject.com/icon/689105/) by Icons fest, [Bulb](https://thenounproject.com/icon/1175583/) by Adrien Coquet, [Date](https://thenounproject.com/icon/1272092/) by Karan, [Success](https://thenounproject.com/icon/3405499/) by Alice Design, [Clock](https://thenounproject.com/icon/2310543/) by Aybige, [Feedback](https://thenounproject.com/icon/651868/) by dilayorganci, [Star](https://thenounproject.com/icon/1368720/) by Zaff Studio, [Tag](https://thenounproject.com/icon/938953/) by Ananth. + +## How to use the plugin + +### Recommended additional plugin + +Editing CSS / HTML code in a textarea is really a pain. Because of that we urgently recommend to install the editor_codemirror plugin (https://github.com/mebis-lp/moodle-editor_codemirror) that is automatically used for editing in tiny_elements management interface then. It helps you with syntax highlighting and code completion. + +### Structure of plugin data + +The main elements of the plugin are **components** (consisting of HTML and CSS code). When users click on them, the corresponding HTML code is inserted into the content area of TinyMCE. + +Every component belongs to exactly one **category**. Each category has some CSS code that is common to all components of the category and some files for the category. + +A category can have multiple **flavors**. Every flavor can also have some CSS code and can be connected to components of the category. Flavors can provide different sub-styles and can be also used as kind of sub-categories. + +Components can also have up to three **variants** that can be switched on/off (multiple variants can be switched on at the same time). Variants have CSS and HTML code. + +### Base templates (delivered with the plugin) + +The plugin has some templates in db/base.zip. They can be imported after installation - feel free to use or to ignore them. + +### Naming + +Every category, component, flavor and variant needs a unique name. Please use lowercase names without spaces and special characters other than "-" and "_" (these names are used as part of CSS class names) and keep in mind, that these names might conflict with existing CSS class names or other existing custom components / categories / ... + +Categories and components will be prefixed by "elements-", flavors and variants written as "elements-..-flavor" / "elements-..-variant" where ".." is the name of the flavor / variant. + +Example for naming: + +Your project is called "guinea pig", so you could call +* your category "guineapig" +* your components "guineapig-title", "guineapig-card", ... +* your flavors "guineapig-with-hat", "guineapig-with-face-mask", ... +* your variants "guineapig-black-white", "guineapig-with-background", ... + +Especially if you want to share your templates with others, it is particularly important to be very careful when chosing names. + +### Creating categories, components, flavors and variants + +To create custom components, go to https://yourmoodle/lib/editor/tiny/plugins/elements/management.php + +#### Categories + +You will need to create at least one category for your components. + +You can enter the following data for your category: + +Name: The internal name (not displayed to the users), should be also used as part of the CSS class name (see "Naming" above). +Display name: The name displayed to the users. +Display order: Categories will be sorted by this number when they are presented to the users (ascending order). +CSS: CSS code that is common for all parts of the category. +Files: Upload the files you want to use in your category (this will mainly be images, maybe fonts too). For images we recommend to use SVG if possible. Do not use files from other categories in your components as this might lead to a lot of problems, especially when doing import / export. + +#### Components + +When creating a component you can enter the following data: + +Name: The internal name (not displayed to the users), should be also used as part of the CSS class name (see "Naming" above). +Display name: The name displayed to the users. +Category: The category the component belongs to. +Code: The HTML code that is inserted to the TinyMCE content area. This can contain the following placeholders: +* {{PLACEHOLDER}} - this is replaced by the currently selected text or (if nothing is selected) a dummy text +* {{CATEGORY}} - the name of the category, prefixed by "elements-" +* {{COMPONENT}} - the name of the component, prefixed by "elements-" +* {{FLAVOR}} - "elements-..-flavor" where ".." is the name of the chosen flavor (is empty, if none is chosen) +* {{VARIANTS}} - the space separated names of the activated variants in the format "elements-..-variant" where ".." is the name of the variant +* {{VARIANTSHTML}} - the concatenated HTML code for all activated variants +* {{@ID}} - a random id + +Text: The default dummy text for {{PLACEHOLDER}} +Variants: Choose the variants available for this component (you can also add them later) +Flavors: The flavors available for this component (you can also add them later) +Display order: Categories will be sorted by this number when they are presented to the users (ascending order). +CSS: CSS code that is used for this component. +JS: Leave this empty for now - Javascript is not implemented in a reliable way yet. +Icon URL: URL of an image that should be shown on the button for the component. To use an image uploaded to the category, click to "Show urls to symbols" below and copy the URL from there (will be like "https://yourmoodle/pluginfile.php/1/tiny_elements/images/..."). Please choose only images from your category. +Hide for students: Check this to show this component only to teachers. + +#### Flavors + +You don't need to have flavors. If you create a flavor, you can enter the following data: + +Name: The internal name (not displayed to the users), will be part of the CSS class name ("elements-..-flavor", see "Naming" above). +Display name: The name displayed to the users. +Category: The category the flavor belongs to. +Display order: Flavors will be sorted by this number when they are presented to the users (ascending order). +CSS: CSS code that is used for this flavor. +Hide for students: Check this to show this flavor only to teachers. + +In addition (after saving), the button icons for components can be changed depending on the flavor. To choose the icons depending on flavor, go to the flavor and click on the rightmost button there. Then you can enter image URLs. + +#### Variants + +You don't need to have variants. If you create a variant, you can enter the following data: + +Name: The internal name (not displayed to the users), will be part of the CSS class name ("elements-..-variant", see "Naming" above). +Display name: The name displayed to the users. +Category: The category the flavor belongs to. +Content: HTML code that is added to the {{VARIANTSHTML}} placeholder. +CSS: CSS code that is used for this variant. +Icon URL: Icon that is displayed on the variant button. It is recommended to use monochromatic images. To use an image uploaded to the category, click to "Show urls to symbols" below and copy the URL from there (will be like "https://yourmoodle/pluginfile.php/1/tiny_elements/images/..."). Please choose only images from your category. +Checkbox for C4L compatibility: Do not use that. + +### Export + +You can either export your complete dataset or one single category. + +The export file is a ZIP file and includes all files in subfolders named like the categories and one XML file that contains the categories, components, flavors and variants. + +This file can be imported at every tiny_elements instance. + +### Import + +You can import any export ZIP file. When you want to import a file, you can simulate the import first to see, what would happen (this is a dry run not changing any data). + +An import replaces existing categories, components, flavors and variants depending on their name (e.g. if you have an existing component "dark-box" and you import a component that is also named "dark-box", the imported one will overwrite the existing one). There is no way to undo an import, so be very careful! diff --git a/amd/build/category_form_helper.min.js b/amd/build/category_form_helper.min.js new file mode 100644 index 0000000..77acda7 --- /dev/null +++ b/amd/build/category_form_helper.min.js @@ -0,0 +1,12 @@ +define("tiny_elements/category_form_helper",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Helper for autocomplete form element to select only variants that belong to the + * selected category. + * + * @module tiny_elements/category_form_helper + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);var _default={processResults:function(selector,results){var options=[];return results.forEach((data=>{options.push({value:data.name,label:data.displayname})})),options},transport:function(selector,query,callback){var _el$dataset$contextid,el=document.querySelector(selector);if(!el)return;const contextid=null!==(_el$dataset$contextid=el.dataset.contextid)&&void 0!==_el$dataset$contextid?_el$dataset$contextid:1,categoryname=el.closest("form").querySelector('select[name="categoryname"]').value;let methodname="tiny_elements_get_variants";"flavors[]"==el.name&&(methodname="tiny_elements_get_flavors"),_ajax.default.call([{methodname:methodname,args:{contextid:contextid,categoryname:categoryname,query:query}}])[0].then(callback).catch(_notification.default.exception)}};return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=category_form_helper.min.js.map \ No newline at end of file diff --git a/amd/build/category_form_helper.min.js.map b/amd/build/category_form_helper.min.js.map new file mode 100644 index 0000000..cded33f --- /dev/null +++ b/amd/build/category_form_helper.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"category_form_helper.min.js","sources":["../src/category_form_helper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper for autocomplete form element to select only variants that belong to the\n * selected category.\n *\n * @module tiny_elements/category_form_helper\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default {\n /**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\n processResults: function(selector, results) {\n var options = [];\n results.forEach((data) => {\n options.push({\n value: data.name,\n label: data.displayname\n });\n });\n return options;\n },\n\n /**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} callback A callback function receiving an array of results.\n */\n /* eslint-disable promise/no-callback-in-promise */\n transport: function(selector, query, callback) {\n var el = document.querySelector(selector);\n if (!el) {\n return;\n }\n const contextid = el.dataset.contextid ?? 1;\n const categoryname = el.closest('form').querySelector('select[name=\"categoryname\"]').value;\n\n let methodname = 'tiny_elements_get_variants';\n if (el.name == 'flavors[]') {\n methodname = 'tiny_elements_get_flavors';\n }\n\n Ajax.call([{\n methodname: methodname,\n args: {\n contextid: contextid,\n categoryname: categoryname,\n query: query,\n }\n }])[0].then(callback).catch(Notification.exception);\n }\n};\n"],"names":["processResults","selector","results","options","forEach","data","push","value","name","label","displayname","transport","query","callback","el","document","querySelector","contextid","dataset","categoryname","closest","methodname","call","args","then","catch","Notification","exception"],"mappings":";;;;;;;;;0LA4Be,CAQXA,eAAgB,SAASC,SAAUC,aAC3BC,QAAU,UACdD,QAAQE,SAASC,OACbF,QAAQG,KAAK,CACTC,MAAOF,KAAKG,KACZC,MAAOJ,KAAKK,iBAGbP,SAWXQ,UAAW,SAASV,SAAUW,MAAOC,oCAC7BC,GAAKC,SAASC,cAAcf,cAC3Ba,gBAGCG,wCAAYH,GAAGI,QAAQD,iEAAa,EACpCE,aAAeL,GAAGM,QAAQ,QAAQJ,cAAc,+BAA+BT,UAEjFc,WAAa,6BACF,aAAXP,GAAGN,OACHa,WAAa,2CAGZC,KAAK,CAAC,CACPD,WAAYA,WACZE,KAAM,CACFN,UAAWA,UACXE,aAAcA,aACdP,MAAOA,UAEX,GAAGY,KAAKX,UAAUY,MAAMC,sBAAaC"} \ No newline at end of file diff --git a/amd/build/commands.min.js b/amd/build/commands.min.js index 1b7a51a..2cec08c 100644 --- a/amd/build/commands.min.js +++ b/amd/build/commands.min.js @@ -1,3 +1,3 @@ -define("tiny_c4l/commands",["exports","editor_tiny/utils","core/str","./ui","./common","./options"],(function(_exports,_utils,_str,_ui,_common,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[c4lButtonNameTitle,c4lMenuItemNameTitle,buttonImage]=await Promise.all([(0,_str.get_string)("button_c4l",_common.component),(0,_str.get_string)("menuitem_c4l",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.isC4LVisible)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addButton(_common.c4lButtonName,{icon:_common.icon,tooltip:c4lButtonNameTitle,onAction:()=>(0,_ui.handleAction)(editor)}),editor.ui.registry.addMenuItem(_common.c4lMenuItemName,{icon:_common.icon,text:c4lMenuItemNameTitle,onAction:()=>(0,_ui.handleAction)(editor)}),editor.options.set("content_style",(0,_options.getpreviewCSS)(editor)))}}})); +define("tiny_elements/commands",["exports","editor_tiny/utils","core/str","tiny_elements/ui","tiny_elements/common","tiny_elements/options"],(function(_exports,_utils,_str,_ui,_common,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[elementsButtonNameTitle,elementsMenuItemNameTitle,buttonImage]=await Promise.all([(0,_str.get_string)("button_elements",_common.component),(0,_str.get_string)("menuitem_elements",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.isElementsVisible)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addButton(_common.elementsButtonName,{icon:_common.icon,tooltip:elementsButtonNameTitle,onAction:()=>(0,_ui.handleAction)(editor)}),editor.ui.registry.addMenuItem(_common.elementsMenuItemName,{icon:_common.icon,text:elementsMenuItemNameTitle,onAction:()=>(0,_ui.handleAction)(editor)}))}}})); //# sourceMappingURL=commands.min.js.map \ No newline at end of file diff --git a/amd/build/commands.min.js.map b/amd/build/commands.min.js.map index ed51981..65e913a 100644 --- a/amd/build/commands.min.js.map +++ b/amd/build/commands.min.js.map @@ -1 +1 @@ -{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L commands.\n *\n * @module tiny_c4l/commands\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {get_string as getString} from 'core/str';\nimport {handleAction} from './ui';\nimport {\n component,\n c4lButtonName,\n c4lMenuItemName,\n icon,\n} from './common';\nimport {\n isC4LVisible,\n getpreviewCSS\n} from './options';\n\nexport const getSetup = async() => {\n const [\n c4lButtonNameTitle,\n c4lMenuItemNameTitle,\n buttonImage,\n ] = await Promise.all([\n getString('button_c4l', component),\n getString('menuitem_c4l', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (isC4LVisible(editor)) {\n // Register the C4L Icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Register the C4L Toolbar Button.\n editor.ui.registry.addButton(c4lButtonName, {\n icon,\n tooltip: c4lButtonNameTitle,\n onAction: () => handleAction(editor),\n });\n\n // Add the C4L Menu Item.\n // This allows it to be added to a standard menu, or a context menu.\n editor.ui.registry.addMenuItem(c4lMenuItemName, {\n icon,\n text: c4lMenuItemNameTitle,\n onAction: () => handleAction(editor),\n });\n\n // Inject custom CSS.\n editor.options.set('content_style', getpreviewCSS(editor));\n }\n };\n};\n"],"names":["_exports","getSetup","async","c4lButtonNameTitle","c4lMenuItemNameTitle","buttonImage","Promise","all","getString","component","getButtonImage","editor","isC4LVisible","ui","registry","addIcon","icon","html","addButton","c4lButtonName","tooltip","onAction","handleAction","addMenuItem","c4lMenuItemName","text","options","set","getpreviewCSS"],"mappings":"0OAwEEA,SAAAC,SAnCsBC,UACpB,MACIC,mBACAC,qBACAC,mBACMC,QAAQC,IAAI,EAClB,EAAAC,KAAAA,YAAU,aAAcC,QAASA,YACjC,EAAAD,KAAAA,YAAU,eAAgBC,QAASA,YACnC,EAAAC,uBAAe,OAAQD,QAASA,aAGpC,OAAQE,UACA,EAAAC,SAAAA,cAAaD,UAEbA,OAAOE,GAAGC,SAASC,QAAQC,QAAAA,KAAMX,YAAYY,MAG7CN,OAAOE,GAAGC,SAASI,UAAUC,sBAAe,CACxCH,KAAAA,QAAIA,KACJI,QAASjB,mBACTkB,SAAUA,KAAM,EAAAC,IAAAA,cAAaX,UAKjCA,OAAOE,GAAGC,SAASS,YAAYC,wBAAiB,CAC5CR,KAAAA,QAAIA,KACJS,KAAMrB,qBACNiB,SAAUA,KAAM,EAAAC,IAAAA,cAAaX,UAIjCA,OAAOe,QAAQC,IAAI,iBAAiB,EAAAC,SAAaA,eAACjB,SACtD,CACH,CACH"} \ No newline at end of file +{"version":3,"file":"commands.min.js","sources":["../src/commands.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements commands.\n *\n * @module tiny_elements/commands\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {get_string as getString} from 'core/str';\nimport {handleAction} from 'tiny_elements/ui';\nimport {\n component,\n elementsButtonName,\n elementsMenuItemName,\n icon,\n} from 'tiny_elements/common';\nimport {isElementsVisible} from 'tiny_elements/options';\n\nexport const getSetup = async() => {\n const [\n elementsButtonNameTitle,\n elementsMenuItemNameTitle,\n buttonImage,\n ] = await Promise.all([\n getString('button_elements', component),\n getString('menuitem_elements', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (isElementsVisible(editor)) {\n // Register the Elements Icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Register the Elements Toolbar Button.\n editor.ui.registry.addButton(elementsButtonName, {\n icon,\n tooltip: elementsButtonNameTitle,\n onAction: () => handleAction(editor),\n });\n\n // Add the Elements Menu Item.\n // This allows it to be added to a standard menu, or a context menu.\n editor.ui.registry.addMenuItem(elementsMenuItemName, {\n icon,\n text: elementsMenuItemNameTitle,\n onAction: () => handleAction(editor),\n });\n }\n };\n};\n"],"names":["async","elementsButtonNameTitle","elementsMenuItemNameTitle","buttonImage","Promise","all","component","editor","ui","registry","addIcon","icon","html","addButton","elementsButtonName","tooltip","onAction","addMenuItem","elementsMenuItemName","text"],"mappings":"qSAkCwBA,gBAEhBC,wBACAC,0BACAC,mBACMC,QAAQC,IAAI,EAClB,mBAAU,kBAAmBC,oBAC7B,mBAAU,oBAAqBA,oBAC/B,yBAAe,OAAQA,4BAGnBC,UACA,8BAAkBA,UAElBA,OAAOC,GAAGC,SAASC,QAAQC,aAAMR,YAAYS,MAG7CL,OAAOC,GAAGC,SAASI,UAAUC,2BAAoB,CAC7CH,KAAAA,aACAI,QAASd,wBACTe,SAAU,KAAM,oBAAaT,UAKjCA,OAAOC,GAAGC,SAASQ,YAAYC,6BAAsB,CACjDP,KAAAA,aACAQ,KAAMjB,0BACNc,SAAU,KAAM,oBAAaT"} \ No newline at end of file diff --git a/amd/build/common.min.js b/amd/build/common.min.js index 7c211d5..05edcda 100644 --- a/amd/build/common.min.js +++ b/amd/build/common.min.js @@ -1,3 +1,11 @@ -define("tiny_c4l/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;_exports.default={component:"tiny_c4l",pluginName:"tiny_c4l/plugin",icon:"tiny_c4l",c4lButtonName:"tiny_c4l",c4lMenuItemName:"tiny_c4l"};return _exports.default})); +define("tiny_elements/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0; +/** + * Tiny Elements common. + * + * @module tiny_elements/common + * @copyright 2022 Marc Català + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +const component="tiny_elements";var _default={component:component,pluginName:"".concat(component,"/plugin"),icon:"".concat(component),elementsButtonName:"".concat(component),elementsMenuItemName:"".concat(component)};return _exports.default=_default,_exports.default})); //# sourceMappingURL=common.min.js.map \ No newline at end of file diff --git a/amd/build/common.min.js.map b/amd/build/common.min.js.map index 1b40d95..5d313c1 100644 --- a/amd/build/common.min.js.map +++ b/amd/build/common.min.js.map @@ -1 +1 @@ -{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L common.\n *\n * @module tiny_c4l/common\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst component = 'tiny_c4l';\n\nexport default {\n component,\n pluginName: `${component}/plugin`,\n icon: `${component}`,\n c4lButtonName: `${component}`,\n c4lMenuItemName: `${component}`,\n};\n"],"names":["_exports","default","component","pluginName","icon","c4lButtonName","c4lMenuItemName"],"mappings":"yIAuB6BA,SAAAC,QAEd,CACXC,UAHc,WAIdC,WAAa,kBACbC,KAAO,WACPC,cAAgB,WAChBC,gBAAkB,YACrB,OAAAN,SAAAC,OAAA"} \ No newline at end of file +{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements common.\n *\n * @module tiny_elements/common\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst component = 'tiny_elements';\n\nexport default {\n component,\n pluginName: `${component}/plugin`,\n icon: `${component}`,\n elementsButtonName: `${component}`,\n elementsMenuItemName: `${component}`,\n};\n"],"names":["component","pluginName","icon","elementsButtonName","elementsMenuItemName"],"mappings":";;;;;;;;MAuBMA,UAAY,6BAEH,CACXA,UAAAA,UACAC,qBAAeD,qBACfE,eAASF,WACTG,6BAAuBH,WACvBI,+BAAyBJ"} \ No newline at end of file diff --git a/amd/build/components.min.js b/amd/build/components.min.js deleted file mode 100644 index d67c0fe..0000000 --- a/amd/build/components.min.js +++ /dev/null @@ -1,11 +0,0 @@ -define("tiny_c4l/components",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0; -/** - * Tiny C4L components. - * - * @module tiny_c4l/components - * @copyright 2022 Marc Català - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -const components=[{id:"0",name:"keyconcept",type:"contextual",imageClass:"c4l-keyconcept-icon",code:'

{{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.",variants:["full-width"]},{id:"1",name:"tip",type:"contextual",imageClass:"c4l-tip-icon",code:'

\n {{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.",variants:["full-width"]},{id:"2",name:"reminder",type:"contextual",imageClass:"c4l-reminder-icon",code:'

\n {{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.",variants:["full-width"]},{id:"3",name:"quote",type:"contextual",imageClass:"c4l-quote-icon",code:'

\n
\n

{{PLACEHOLDER}}

\n
\n {{VARIANTSHTML}}\n


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus. Praesent dictum in velit sed dapibus.",variants:["full-width","quote"]},{id:"4",name:"dodontcards",type:"contextual",imageClass:"c4l-dodontcards-icon",code:'

\n
{{PLACEHOLDER}}
\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Phasellus a posuere nibh, eu mollis lacus.\n Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes,\n nascetur ridiculus mus.


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus. Praesent dictum in velit sed dapibus.Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",variants:["full-width"]},{id:"5",name:"readingcontext",type:"contextual",imageClass:"c4l-readingcontext-icon",code:'

\n

{{PLACEHOLDER}}

{{VARIANTSHTML}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus leo, hendrerit ac sem vitae, posuere egestas nisi. Lorem ipsum dolor sit amet. Phasellus leo, hendrerit ac sem vitae, posuere egestas nisi.",variants:["full-width","quote","comfort-reading"]},{id:"6",name:"example",type:"contextual",imageClass:"c4l-example-icon",code:'

Lorem ipsum dolor sit amet

\n

{{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus. Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",variants:["full-width"]},{id:"7",name:"figure",type:"contextual",imageClass:"c4l-figure-icon",code:'

Lorem ipsum dolor sit amet\n {{VARIANTSHTML}}


',text:"Consectetur adipiscing elit.",variants:["full-width","caption"]},{id:"8",name:"tag",type:"contextual",imageClass:"c4l-tag-icon",code:'

\n
{{PLACEHOLDER}}
',text:"Lorem ipsum",variants:["align-right"]},{id:"9",name:"inlinetag",type:"contextual",imageClass:"c4l-inlinetag-icon",code:'{{PLACEHOLDER}}',text:"Text",variants:[]},{id:"10",name:"attention",type:"procedural",imageClass:"c4l-attention-icon",code:'

\n "{{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.",variants:["full-width"]},{id:"11",name:"estimatedtime",type:"procedural",imageClass:"c4l-estimatedtime-icon",code:'

{{PLACEHOLDER}} {{#min}}
',text:"15",variants:["align-left"]},{id:"12",name:"duedate",type:"procedural",imageClass:"c4l-duedate-icon",code:'

{{PLACEHOLDER}}
',text:"November 17th",variants:["align-left"]},{id:"13",name:"proceduralcontext",type:"procedural",imageClass:"c4l-proceduralcontext-icon",code:'

\n {{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus. Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla quis lorem aliquet, fermentum dolor ac, venenatis turpis.",variants:["full-width"]},{id:"14",name:"learningoutcomes",type:"procedural",imageClass:"c4l-learningoutcomes-icon",code:'

\n
\n
{{#learningoutcomes}}
\n
  • {{PLACEHOLDER}}
  • Curabitur non nulla sit amet\n nisl tempus convallis quis ac lectus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.
  • \n
  • Nulla porttitor accumsan tincidunt. Curabitur aliquet quam id dui posuere blandit.\n Curabitur non nulla sit amet nisl tempus convallis quis ac lectus.


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut porta, neque id feugiat consectetur, enim ipsum tincidunt nunc, id suscipit mauris urna sit amet lectus.",variants:["full-width","ordered-list"]},{id:"15",name:"gradingvalue",type:"evaluative",imageClass:"c4l-gradingvalue-icon",code:'

{{#gradingvalue}}: {{PLACEHOLDER}}
',text:"33.3%",variants:["align-left"]},{id:"16",name:"expectedfeedback",type:"evaluative",imageClass:"c4l-expectedfeedback-icon",code:'

\n

{{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus. Praesent dictum in velit sed dapibus.",variants:["full-width"]},{id:"17",name:"allpurposecard",type:"helper",imageClass:"c4l-allpurposecard-icon",code:'

{{PLACEHOLDER}}


',text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus. Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",variants:["full-width"]}];_exports.default={components:components};return _exports.default})); - -//# sourceMappingURL=components.min.js.map \ No newline at end of file diff --git a/amd/build/components.min.js.map b/amd/build/components.min.js.map deleted file mode 100644 index 23dd4fd..0000000 --- a/amd/build/components.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"components.min.js","sources":["../src/components.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L components.\n *\n * @module tiny_c4l/components\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst components = [\n {\n id: \"0\",\n name: \"keyconcept\",\n type: \"contextual\",\n imageClass: \"c4l-keyconcept-icon\",\n code:\n '

' +\n \"{{PLACEHOLDER}}


\",\n text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.\",\n variants: [\"full-width\"],\n },\n {\n id: \"1\",\n name: \"tip\",\n type: \"contextual\",\n imageClass: \"c4l-tip-icon\",\n code:\n `

\n {{PLACEHOLDER}}


`,\n text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.\",\n variants: [\"full-width\"],\n },\n {\n id: \"2\",\n name: \"reminder\",\n type: \"contextual\",\n imageClass: \"c4l-reminder-icon\",\n code:\n `

\n {{PLACEHOLDER}}


`,\n text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.\",\n variants: [\"full-width\"],\n },\n {\n id: \"3\",\n name: \"quote\",\n type: \"contextual\",\n imageClass: \"c4l-quote-icon\",\n code:\n `

\n
\n

{{PLACEHOLDER}}

\n
\n {{VARIANTSHTML}}\n


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus.\" +\n \" Praesent dictum in velit sed dapibus.\",\n variants: [\"full-width\", \"quote\"],\n },\n {\n id: \"4\",\n name: \"dodontcards\",\n type: \"contextual\",\n imageClass: \"c4l-dodontcards-icon\",\n code:\n `

\n
{{PLACEHOLDER}}
\n
\n Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Phasellus a posuere nibh, eu mollis lacus.\n Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes,\n nascetur ridiculus mus.


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus.\" +\n \" Praesent dictum in velit sed dapibus.\" +\n \"Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.\",\n variants: [\"full-width\"],\n },\n {\n id: \"5\",\n name: \"readingcontext\",\n type: \"contextual\",\n imageClass: \"c4l-readingcontext-icon\",\n code:\n `

\n

{{PLACEHOLDER}}

{{VARIANTSHTML}}


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus leo, hendrerit ac sem vitae,\" +\n \" posuere egestas nisi. Lorem ipsum dolor sit amet. \" +\n \"Phasellus leo, hendrerit ac sem vitae, posuere egestas nisi.\",\n variants: [\"full-width\", \"quote\", \"comfort-reading\"],\n },\n {\n id: \"6\",\n name: \"example\",\n type: \"contextual\",\n imageClass: \"c4l-example-icon\",\n code:\n `

Lorem ipsum dolor sit amet

\n

{{PLACEHOLDER}}


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\" +\n \" Phasellus a posuere nibh, eu mollis lacus.\" +\n \" Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes,\" +\n \" nascetur ridiculus mus.\",\n variants: [\"full-width\"],\n },\n {\n id: \"7\",\n name: \"figure\",\n type: \"contextual\",\n imageClass: \"c4l-figure-icon\",\n code:\n `

\"Lorem\n {{VARIANTSHTML}}


`,\n text: \"Consectetur adipiscing elit.\",\n variants: [\"full-width\", \"caption\"],\n },\n {\n id: \"8\",\n name: \"tag\",\n type: \"contextual\",\n imageClass: \"c4l-tag-icon\",\n code:\n `

\n
{{PLACEHOLDER}}
`,\n text: \"Lorem ipsum\",\n variants: [\"align-right\"],\n },\n {\n id: \"9\",\n name: \"inlinetag\",\n type: \"contextual\",\n imageClass: \"c4l-inlinetag-icon\",\n code: `{{PLACEHOLDER}}`,\n text: \"Text\",\n variants: [],\n },\n {\n id: \"10\",\n name: \"attention\",\n type: \"procedural\",\n imageClass: \"c4l-attention-icon\",\n code:\n `

\n \"{{PLACEHOLDER}}


`,\n text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.\",\n variants: [\"full-width\"],\n },\n {\n id: \"11\",\n name: \"estimatedtime\",\n type: \"procedural\",\n imageClass: \"c4l-estimatedtime-icon\",\n code:\n `

{{PLACEHOLDER}} {{#min}}
`,\n text: \"15\",\n variants: [\"align-left\"],\n },\n {\n id: \"12\",\n name: \"duedate\",\n type: \"procedural\",\n imageClass: \"c4l-duedate-icon\",\n code:\n `

{{PLACEHOLDER}}
`,\n text: \"November 17th\",\n variants: [\"align-left\"],\n },\n {\n id: \"13\",\n name: \"proceduralcontext\",\n type: \"procedural\",\n imageClass: \"c4l-proceduralcontext-icon\",\n code:\n `

\n {{PLACEHOLDER}}


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus.\" +\n \" Praesent dictum in velit sed dapibus.\" +\n \" Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla quis lorem aliquet,\" +\n \" fermentum dolor ac, venenatis turpis.\",\n variants: [\"full-width\"],\n },\n {\n id: \"14\",\n name: \"learningoutcomes\",\n type: \"procedural\",\n imageClass: \"c4l-learningoutcomes-icon\",\n code:\n `

\n
\n
{{#learningoutcomes}}
\n
  • {{PLACEHOLDER}}
  • Curabitur non nulla sit amet\n nisl tempus convallis quis ac lectus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.
  • \n
  • Nulla porttitor accumsan tincidunt. Curabitur aliquet quam id dui posuere blandit.\n Curabitur non nulla sit amet nisl tempus convallis quis ac lectus.


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut porta, neque id feugiat consectetur, \" +\n \"enim ipsum tincidunt nunc, id suscipit mauris urna sit amet lectus.\",\n variants: [\"full-width\", \"ordered-list\"],\n },\n {\n id: \"15\",\n name: \"gradingvalue\",\n type: \"evaluative\",\n imageClass: \"c4l-gradingvalue-icon\",\n code:\n `

{{#gradingvalue}}: {{PLACEHOLDER}}
`,\n text: \"33.3%\",\n variants: [\"align-left\"],\n },\n {\n id: \"16\",\n name: \"expectedfeedback\",\n type: \"evaluative\",\n imageClass: \"c4l-expectedfeedback-icon\",\n code:\n `

\n

{{PLACEHOLDER}}


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus.\" +\n \" Praesent dictum in velit sed dapibus.\",\n variants: [\"full-width\"],\n },\n {\n id: \"17\",\n name: \"allpurposecard\",\n type: \"helper\",\n imageClass: \"c4l-allpurposecard-icon\",\n code:\n `

{{PLACEHOLDER}}


`,\n text:\n \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus.\" +\n \" Praesent dictum in velit sed dapibus.\" +\n \" Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.\",\n variants: [\"full-width\"],\n },\n];\n\nexport default {\n components,\n};\n"],"names":["components","id","name","type","imageClass","code","text","variants","_exports","default"],"mappings":";;;;;;;;AAuBA,MAAMA,WAAa,CACf,CACIC,GAAI,IACJC,KAAM,aACNC,KAAM,aACNC,WAAY,sBACZC,KACI,oIAEJC,KAAM,0GACNC,SAAU,CAAC,eAEf,CACIN,GAAI,IACJC,KAAM,MACNC,KAAM,aACNC,WAAY,eACZC,KACK,oIAELC,KAAM,0GACNC,SAAU,CAAC,eAEf,CACIN,GAAI,IACJC,KAAM,WACNC,KAAM,aACNC,WAAY,oBACZC,KACK,4JAGLC,KAAM,0GACNC,SAAU,CAAC,eAEf,CACIN,GAAI,IACJC,KAAM,QACNC,KAAM,aACNC,WAAY,iBACZC,KACK,0UAOLC,KACI,4IAEJC,SAAU,CAAC,aAAc,UAE7B,CACIN,GAAI,IACJC,KAAM,cACNC,KAAM,aACNC,WAAY,uBACZC,KACK,wkBAQLC,KACI,kOAGJC,SAAU,CAAC,eAEf,CACIN,GAAI,IACJC,KAAM,iBACNC,KAAM,aACNC,WAAY,0BACZC,KACK,8LAGLC,KACI,iNAGJC,SAAU,CAAC,aAAc,QAAS,oBAEtC,CACIN,GAAI,IACJC,KAAM,UACNC,KAAM,aACNC,WAAY,mBACZC,KACK,oMAGLC,KACI,mOAIJC,SAAU,CAAC,eAEf,CACIN,GAAI,IACJC,KAAM,SACNC,KAAM,aACNC,WAAY,kBACZC,KACK,2MAGLC,KAAM,+BACNC,SAAU,CAAC,aAAc,YAE7B,CACIN,GAAI,IACJC,KAAM,MACNC,KAAM,aACNC,WAAY,eACZC,KACK,6JAELC,KAAM,cACNC,SAAU,CAAC,gBAEf,CACIN,GAAI,IACJC,KAAM,YACNC,KAAM,aACNC,WAAY,qBACZC,KAAO,+FACPC,KAAM,OACNC,SAAU,IAEd,CACIN,GAAI,KACJC,KAAM,YACNC,KAAM,aACNC,WAAY,qBACZC,KACK,iJAELC,KAAM,0GACNC,SAAU,CAAC,eAEf,CACIN,GAAI,KACJC,KAAM,gBACNC,KAAM,aACNC,WAAY,yBACZC,KACK,sMAELC,KAAM,KACNC,SAAU,CAAC,eAEf,CACIN,GAAI,KACJC,KAAM,UACNC,KAAM,aACNC,WAAY,mBACZC,KACK,oKAELC,KAAM,gBACNC,SAAU,CAAC,eAEf,CACIN,GAAI,KACJC,KAAM,oBACNC,KAAM,aACNC,WAAY,6BACZC,KACK,0KAGLC,KACI,mSAIJC,SAAU,CAAC,eAEf,CACIN,GAAI,KACJC,KAAM,mBACNC,KAAM,aACNC,WAAY,4BACZC,KACK,yoBAOLC,KACI,uKAEJC,SAAU,CAAC,aAAc,iBAE7B,CACIN,GAAI,KACJC,KAAM,eACNC,KAAM,aACNC,WAAY,wBACZC,KACK,+MAELC,KAAM,QACNC,SAAU,CAAC,eAEf,CACIN,GAAI,KACJC,KAAM,mBACNC,KAAM,aACNC,WAAY,4BACZC,KACK,kLAGLC,KACI,4IAEJC,SAAU,CAAC,eAEf,CACIN,GAAI,KACJC,KAAM,iBACNC,KAAM,SACNC,WAAY,0BACZC,KACK,gKAELC,KACI,mOAGJC,SAAU,CAAC,gBAEjBC,SAAAC,QAEa,CACXT,uBACH,OAAAQ,SAAAC,OAAA"} \ No newline at end of file diff --git a/amd/build/configuration.min.js b/amd/build/configuration.min.js index 0a87d57..6ab723c 100644 --- a/amd/build/configuration.min.js +++ b/amd/build/configuration.min.js @@ -1,11 +1,11 @@ -define("tiny_c4l/configuration",["exports","./common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0; +define("tiny_elements/configuration",["exports","tiny_elements/common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0; /** - * Tiny C4L configuration. + * Tiny Elements configuration. * - * @module tiny_c4l/configuration + * @module tiny_elements/configuration * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const configureMenu=menu=>{const items=menu.insert.items.split(" ");return items.some(((item,index)=>!!item.match(/(link)\b/)&&(items.splice(index+1,0,_common.component),!0)))?menu.insert.items=items.join(" "):(0,_utils.addMenubarItem)(menu,"insert",_common.component),menu};_exports.configure=instanceConfig=>{return{menu:configureMenu(instanceConfig.menu),toolbar:(toolbar=instanceConfig.toolbar,toolbar.map((section=>("content"===section.name&§ion.items.unshift(_common.component),section))))};var toolbar}})); +const configureMenu=menu=>{const items=menu.insert.items.split(" ");return items.some(((item,index)=>!!item.match(/(link)\b/)&&(items.splice(index+1,0,_common.component),!0)))?menu.insert.items=items.join(" "):(0,_utils.addMenubarItem)(menu,"insert",_common.component),menu};_exports.configure=(instanceConfig,options)=>{return instanceConfig.content_css.push(options.plugins["tiny_elements/plugin"].config.cssurl),{menu:configureMenu(instanceConfig.menu),toolbar:(toolbar=instanceConfig.toolbar,toolbar.map((section=>("content"===section.name&§ion.items.unshift(_common.component),section))))};var toolbar}})); //# sourceMappingURL=configuration.min.js.map \ No newline at end of file diff --git a/amd/build/configuration.min.js.map b/amd/build/configuration.min.js.map index 5a6142f..27c360b 100644 --- a/amd/build/configuration.min.js.map +++ b/amd/build/configuration.min.js.map @@ -1 +1 @@ -{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L configuration.\n *\n * @module tiny_c4l/configuration\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {component as c4lButtonName} from './common';\nimport {addMenubarItem} from 'editor_tiny/utils';\n\nconst configureMenu = (menu) => {\n const items = menu.insert.items.split(' ');\n const inserted = items.some((item, index) => {\n // Append after the link button.\n if (item.match(/(link)\\b/)) {\n items.splice(index + 1, 0, c4lButtonName);\n return true;\n }\n\n return false;\n });\n\n if (inserted) {\n menu.insert.items = items.join(' ');\n } else {\n addMenubarItem(menu, 'insert', c4lButtonName);\n }\n\n return menu;\n};\n\nconst configureToolbar = (toolbar) => {\n // The toolbar contains an array of named sections.\n // The Moodle integration ensures that there is a section called 'content'.\n\n return toolbar.map((section) => {\n if (section.name === 'content') {\n // Insert the c4l button at the start of it.\n section.items.unshift(c4lButtonName);\n }\n\n return section;\n });\n};\n\nexport const configure = (instanceConfig) => {\n return {\n menu: configureMenu(instanceConfig.menu),\n toolbar: configureToolbar(instanceConfig.toolbar),\n };\n};\n"],"names":["configureMenu","menu","items","insert","split","some","item","index","match","splice","c4lButtonName","join","addMenubarItem","_exports","configure","instanceConfig","toolbar","map","section","name","unshift"],"mappings":";;;;;;;;AA0BA,MAAMA,cAAiBC,OACnB,MAAMC,MAAQD,KAAKE,OAAOD,MAAME,MAAM,KAiBtC,OAhBiBF,MAAMG,MAAK,CAACC,KAAMC,UAE3BD,KAAKE,MAAM,cACXN,MAAMO,OAAOF,MAAQ,EAAG,EAAGG,QAAAA,YACpB,KAOXT,KAAKE,OAAOD,MAAQA,MAAMS,KAAK,MAE/B,EAAAC,uBAAeX,KAAM,SAAUS,mBAG5BT,IAAI,EAsBbY,SAAAC,UALwBC,iBACtB,MAAO,CACHd,KAAMD,cAAce,eAAed,MACnCe,SAjBkBA,QAiBQD,eAAeC,QAbtCA,QAAQC,KAAKC,UACK,YAAjBA,QAAQC,MAERD,QAAQhB,MAAMkB,QAAQV,mBAGnBQ,aAVWF,WAkBrB,CACH"} \ No newline at end of file +{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements configuration.\n *\n * @module tiny_elements/configuration\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {component as elementsButtonName} from 'tiny_elements/common';\nimport {addMenubarItem} from 'editor_tiny/utils';\n\nconst configureMenu = (menu) => {\n const items = menu.insert.items.split(' ');\n const inserted = items.some((item, index) => {\n // Append after the link button.\n if (item.match(/(link)\\b/)) {\n items.splice(index + 1, 0, elementsButtonName);\n return true;\n }\n\n return false;\n });\n\n if (inserted) {\n menu.insert.items = items.join(' ');\n } else {\n addMenubarItem(menu, 'insert', elementsButtonName);\n }\n\n return menu;\n};\n\nconst configureToolbar = (toolbar) => {\n // The toolbar contains an array of named sections.\n // The Moodle integration ensures that there is a section called 'content'.\n\n return toolbar.map((section) => {\n if (section.name === 'content') {\n // Insert the elements button at the start of it.\n section.items.unshift(elementsButtonName);\n }\n\n return section;\n });\n};\n\nexport const configure = (instanceConfig, options) => {\n instanceConfig.content_css.push(options.plugins['tiny_elements/plugin'].config.cssurl);\n return {\n menu: configureMenu(instanceConfig.menu),\n toolbar: configureToolbar(instanceConfig.toolbar),\n };\n};\n"],"names":["configureMenu","menu","items","insert","split","some","item","index","match","splice","elementsButtonName","join","instanceConfig","options","content_css","push","plugins","config","cssurl","toolbar","map","section","name","unshift"],"mappings":";;;;;;;;MA0BMA,cAAiBC,aACbC,MAAQD,KAAKE,OAAOD,MAAME,MAAM,YACrBF,MAAMG,MAAK,CAACC,KAAMC,UAE3BD,KAAKE,MAAM,cACXN,MAAMO,OAAOF,MAAQ,EAAG,EAAGG,oBACpB,KAOXT,KAAKE,OAAOD,MAAQA,MAAMS,KAAK,+BAEhBV,KAAM,SAAUS,mBAG5BT,yBAiBc,CAACW,eAAgBC,kBACtCD,eAAeE,YAAYC,KAAKF,QAAQG,QAAQ,wBAAwBC,OAAOC,QACxE,CACHjB,KAAMD,cAAcY,eAAeX,MACnCkB,SAlBkBA,QAkBQP,eAAeO,QAdtCA,QAAQC,KAAKC,UACK,YAAjBA,QAAQC,MAERD,QAAQnB,MAAMqB,QAAQb,mBAGnBW,aAVWF,IAAAA"} \ No newline at end of file diff --git a/amd/build/data.min.js b/amd/build/data.min.js new file mode 100644 index 0000000..fe7797a --- /dev/null +++ b/amd/build/data.min.js @@ -0,0 +1,3 @@ +define("tiny_elements/data",["exports","core/str","tiny_elements/common","./variantslib","./helper","core/ajax","core/log"],(function(_exports,_str,_common,_variantslib,_helper,_ajax,_log){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_log=(obj=_log)&&obj.__esModule?obj:{default:obj};return _exports.default=class{constructor(contextid,userStudent,previewElements,canManage){_defineProperty(this,"categories",[]),_defineProperty(this,"components",[]),_defineProperty(this,"flavors",[]),_defineProperty(this,"variants",[]),_defineProperty(this,"langStrings",{}),_defineProperty(this,"userStudent",!1),_defineProperty(this,"canManage",!1),_defineProperty(this,"contextid",1),this.contextid=contextid,this.userStudent=userStudent,this.previewElements=previewElements,this.canManage=canManage,(0,_variantslib.setData)(this)}async loadData(){await this.loadElementsData(),this.langStrings=await this.getAllStrings()}getComponents(){return this.components}getFlavors(){return this.flavors}getVariants(){return this.variants}getComponentById(id){return(0,_helper.findById)(this.components,id)}getCategoryFlavors(categoryname){const categoryFlavors=[];return this.flavors.forEach((flavor=>{flavor.categoryname==categoryname&&categoryFlavors.push({id:flavor.id,name:flavor.name,displayname:flavor.displayname,displayorder:flavor.displayorder})})),categoryFlavors}getCategories(){const cats=[];return this.categories.forEach((category=>{let categoryFlavors=this.getCategoryFlavors(category.name);categoryFlavors.sort(((a,b)=>a.displayorder-b.displayorder));let hasFlavors=Array.isArray(categoryFlavors)&&categoryFlavors.length;cats.push({categoryid:category.id,name:category.displayname,categoryname:category.name,type:category.id,displayorder:category.displayorder,flavors:categoryFlavors,hasFlavors:hasFlavors,active:""})})),cats.sort(((a,b)=>a.displayorder-b.displayorder)),cats.length>0&&(cats[0].active="active",cats[0].flavors.length>0&&(cats[0].flavors[0].factive="active")),cats}getComponentVariants(component){const componentVariants=[];return component.variants.forEach((variant=>{let variantitem=(0,_helper.findByName)(this.variants,variant);if(void 0!==variantitem){let state=(0,_variantslib.variantExists)(component.name,variantitem.name)?"on":"off";componentVariants.push({id:variantitem.id,name:variantitem.name,displayname:variantitem.displayname,state:state,imageClass:variantitem.name+"-variant-"+state,variantclass:(variantitem.c4lcompatibility?"c4l":"elements")+"-"+variantitem.name+"-variant",title:this.langStrings.get(variantitem.name),content:variantitem.content})}})),componentVariants.sort(((a,b)=>a.name.localeCompare(b.name))),componentVariants}getCategoryById(id){return(0,_helper.findById)(this.categories,id)}getLangString(id){return this.langStrings.get(id)}getButtons(editor){const buttons=[];editor.selection.getContent();return Object.values(this.components).forEach((component=>{buttons.push({id:component.id,name:component.displayname,type:component.categoryname,imageClass:"elements-"+component.name+"-icon",htmlcode:component.code,variants:this.getComponentVariants(component),flavorlist:component.flavors.join(","),category:component.categoryname,displayorder:component.displayorder})})),buttons.sort(((a,b)=>a.displayorder-b.displayorder)),buttons}getTemplateContext(editor){return Object.assign({},{elementid:editor.id,buttons:this.getButtons(editor),categories:this.getCategories(),preview:this.previewElements,canmanage:this.canManage})}getPreviewElements(){return this.previewElements}async getAllStrings(){const keys=[],compRegex=/{{#([^}]*)}}/g;this.components.forEach((element=>{[...element.code.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])})),[...element.text.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])}))}));const stringValues=await(0,_str.get_strings)(keys.map((key=>({key:key,pluginname:_common.component}))));return new Map(keys.map(((key,index)=>[key,stringValues[index]])))}async loadElementsData(){const data=await(0,_ajax.call)([{methodname:"tiny_elements_get_elements_data",args:{isstudent:this.userStudent,contextid:this.contextid}}])[0].catch((err=>{_log.default.error(err.message)})),indexedComponents=[];data.components.forEach((component=>{indexedComponents[component.id]=component}));const indexedVariants=[];data.variants.forEach((variant=>{indexedVariants[variant.id]=variant}));const indexedCategories=[];data.categories.forEach((category=>{indexedCategories[category.id]=category})),this.components=indexedComponents,this.variants=indexedVariants,this.categories=indexedCategories,this.flavors=data.flavors}},_exports.default})); + +//# sourceMappingURL=data.min.js.map \ No newline at end of file diff --git a/amd/build/data.min.js.map b/amd/build/data.min.js.map new file mode 100644 index 0000000..ae934f2 --- /dev/null +++ b/amd/build/data.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"data.min.js","sources":["../src/data.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Container for tiny_elements data (categories, components, flavors, variants).\n *\n * @module tiny_elements/data\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_strings as getStrings} from 'core/str';\nimport {component as pluginname} from 'tiny_elements/common';\nimport {\n variantExists,\n setData as setVariantsData\n} from './variantslib';\nimport {\n findById,\n findByName\n} from './helper';\nimport {call as fetchMany} from 'core/ajax';\nimport Log from 'core/log';\n\nexport default class Data {\n categories = [];\n components = [];\n flavors = [];\n variants = [];\n langStrings = {};\n userStudent = false;\n canManage = false;\n contextid = 1;\n\n constructor(contextid, userStudent, previewElements, canManage) {\n this.contextid = contextid;\n this.userStudent = userStudent;\n this.previewElements = previewElements;\n this.canManage = canManage;\n setVariantsData(this);\n }\n\n async loadData() {\n await this.loadElementsData();\n this.langStrings = await this.getAllStrings();\n }\n\n getComponents() {\n return this.components;\n }\n\n getFlavors() {\n return this.flavors;\n }\n\n getVariants() {\n return this.variants;\n }\n\n getComponentById(id) {\n return findById(this.components, id);\n }\n\n getCategoryFlavors(categoryname) {\n const categoryFlavors = [];\n this.flavors.forEach(flavor => {\n if (flavor.categoryname == categoryname) {\n categoryFlavors.push({\n id: flavor.id,\n name: flavor.name,\n displayname: flavor.displayname,\n displayorder: flavor.displayorder,\n });\n }\n });\n return categoryFlavors;\n }\n\n /**\n * Get the Elements categories for the dialogue.\n *\n * @returns {object} data\n */\n getCategories() {\n const cats = [];\n // Iterate over contexts.\n this.categories.forEach((category) => {\n let categoryFlavors = this.getCategoryFlavors(category.name);\n categoryFlavors.sort((a, b) => a.displayorder - b.displayorder);\n let hasFlavors = Array.isArray(categoryFlavors) && categoryFlavors.length;\n cats.push({\n categoryid: category.id,\n name: category.displayname,\n categoryname: category.name,\n type: category.id,\n displayorder: category.displayorder,\n flavors: categoryFlavors,\n hasFlavors: hasFlavors,\n active: '',\n });\n });\n // Sort by displayorder and set first to active.\n cats.sort((a, b) => a.displayorder - b.displayorder);\n if (cats.length > 0) {\n cats[0].active = 'active';\n if (cats[0].flavors.length > 0) {\n cats[0].flavors[0].factive = 'active';\n }\n }\n\n return cats;\n }\n\n getComponentVariants(component) {\n const componentVariants = [];\n component.variants.forEach(variant => {\n let variantitem = findByName(this.variants, variant);\n if (variantitem !== undefined) {\n let state = variantExists(component.name, variantitem.name) ? 'on' : 'off';\n componentVariants.push({\n id: variantitem.id,\n name: variantitem.name,\n displayname: variantitem.displayname,\n state: state,\n imageClass: variantitem.name + '-variant-' + state,\n variantclass: (variantitem.c4lcompatibility ? 'c4l' : 'elements') + '-' + variantitem.name + '-variant',\n title: this.langStrings.get(variantitem.name),\n content: variantitem.content,\n });\n }\n });\n componentVariants.sort((a, b) => (a.name.localeCompare(b.name)));\n return componentVariants;\n }\n\n getCategoryById(id) {\n return findById(this.categories, id);\n }\n\n getLangString(id) {\n return this.langStrings.get(id);\n }\n\n /**\n * Get the Elements buttons for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} buttons\n */\n getButtons(editor) {\n const buttons = [];\n // Not used at the moment.\n // eslint-disable-next-line no-unused-vars\n const sel = editor.selection.getContent();\n Object.values(this.components).forEach(component => {\n buttons.push({\n id: component.id,\n name: component.displayname,\n type: component.categoryname,\n imageClass: 'elements-' + component.name + '-icon',\n htmlcode: component.code,\n variants: this.getComponentVariants(component),\n flavorlist: component.flavors.join(','),\n category: component.categoryname,\n displayorder: component.displayorder,\n });\n });\n buttons.sort((a, b) => a.displayorder - b.displayorder);\n\n return buttons;\n }\n\n /**\n * Get the template context for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} data\n */\n getTemplateContext(editor) {\n return Object.assign({}, {\n elementid: editor.id,\n buttons: this.getButtons(editor),\n categories: this.getCategories(),\n preview: this.previewElements,\n canmanage: this.canManage,\n });\n }\n\n getPreviewElements() {\n return this.previewElements;\n }\n\n /**\n * Get language strings.\n *\n * @return {object} Language strings\n */\n async getAllStrings() {\n const keys = [];\n const compRegex = /{{#([^}]*)}}/g;\n\n this.components.forEach(element => {\n // Get lang strings from components.\n [...element.code.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n\n // Get lang strings from text placeholders.\n [...element.text.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n });\n\n const stringValues = await getStrings(keys.map((key) => ({key, pluginname})));\n return new Map(keys.map((key, index) => ([key, stringValues[index]])));\n }\n\n async loadElementsData() {\n const data = await fetchMany([{\n methodname: 'tiny_elements_get_elements_data',\n args: {\n isstudent: this.userStudent,\n contextid: this.contextid\n },\n }])[0].catch(err => {\n Log.error(err.message);\n });\n\n const indexedComponents = [];\n data.components.forEach(component => {\n indexedComponents[component.id] = component;\n });\n\n const indexedVariants = [];\n data.variants.forEach(variant => {\n indexedVariants[variant.id] = variant;\n });\n\n const indexedCategories = [];\n data.categories.forEach(category => {\n indexedCategories[category.id] = category;\n });\n\n this.components = indexedComponents;\n this.variants = indexedVariants;\n this.categories = indexedCategories;\n this.flavors = data.flavors;\n }\n}\n"],"names":["constructor","contextid","userStudent","previewElements","canManage","this","loadElementsData","langStrings","getAllStrings","getComponents","components","getFlavors","flavors","getVariants","variants","getComponentById","id","getCategoryFlavors","categoryname","categoryFlavors","forEach","flavor","push","name","displayname","displayorder","getCategories","cats","categories","category","sort","a","b","hasFlavors","Array","isArray","length","categoryid","type","active","factive","getComponentVariants","component","componentVariants","variant","variantitem","undefined","state","imageClass","variantclass","c4lcompatibility","title","get","content","localeCompare","getCategoryById","getLangString","getButtons","editor","buttons","selection","getContent","Object","values","htmlcode","code","flavorlist","join","getTemplateContext","assign","elementid","preview","canmanage","getPreviewElements","keys","compRegex","element","matchAll","strLang","indexOf","text","stringValues","map","key","pluginname","Map","index","data","methodname","args","isstudent","catch","err","error","message","indexedComponents","indexedVariants","indexedCategories"],"mappings":"ygBA+CIA,YAAYC,UAAWC,YAAaC,gBAAiBC,6CATxC,sCACA,mCACH,oCACC,uCACG,wCACA,qCACF,oCACA,QAGHH,UAAYA,eACZC,YAAcA,iBACdC,gBAAkBA,qBAClBC,UAAYA,mCACDC,6BAIVA,KAAKC,wBACNC,kBAAoBF,KAAKG,gBAGlCC,uBACWJ,KAAKK,WAGhBC,oBACWN,KAAKO,QAGhBC,qBACWR,KAAKS,SAGhBC,iBAAiBC,WACN,oBAASX,KAAKK,WAAYM,IAGrCC,mBAAmBC,oBACTC,gBAAkB,eACnBP,QAAQQ,SAAQC,SACbA,OAAOH,cAAgBA,cACvBC,gBAAgBG,KAAK,CACjBN,GAAIK,OAAOL,GACXO,KAAMF,OAAOE,KACbC,YAAaH,OAAOG,YACpBC,aAAcJ,OAAOI,kBAI1BN,gBAQXO,sBACUC,KAAO,eAERC,WAAWR,SAASS,eACjBV,gBAAkBd,KAAKY,mBAAmBY,SAASN,MACvDJ,gBAAgBW,MAAK,CAACC,EAAGC,IAAMD,EAAEN,aAAeO,EAAEP,mBAC9CQ,WAAaC,MAAMC,QAAQhB,kBAAoBA,gBAAgBiB,OACnET,KAAKL,KAAK,CACNe,WAAYR,SAASb,GACrBO,KAAMM,SAASL,YACfN,aAAcW,SAASN,KACvBe,KAAMT,SAASb,GACfS,aAAcI,SAASJ,aACvBb,QAASO,gBACTc,WAAYA,WACZM,OAAQ,QAIhBZ,KAAKG,MAAK,CAACC,EAAGC,IAAMD,EAAEN,aAAeO,EAAEP,eACnCE,KAAKS,OAAS,IACdT,KAAK,GAAGY,OAAS,SACbZ,KAAK,GAAGf,QAAQwB,OAAS,IACzBT,KAAK,GAAGf,QAAQ,GAAG4B,QAAU,WAI9Bb,KAGXc,qBAAqBC,iBACXC,kBAAoB,UAC1BD,UAAU5B,SAASM,SAAQwB,cACnBC,aAAc,sBAAWxC,KAAKS,SAAU8B,iBACxBE,IAAhBD,YAA2B,KACvBE,OAAQ,8BAAcL,UAAUnB,KAAMsB,YAAYtB,MAAQ,KAAO,MACrEoB,kBAAkBrB,KAAK,CACnBN,GAAI6B,YAAY7B,GAChBO,KAAMsB,YAAYtB,KAClBC,YAAaqB,YAAYrB,YACzBuB,MAAOA,MACPC,WAAYH,YAAYtB,KAAO,YAAcwB,MAC7CE,cAAeJ,YAAYK,iBAAmB,MAAQ,YAAc,IAAML,YAAYtB,KAAO,WAC7F4B,MAAO9C,KAAKE,YAAY6C,IAAIP,YAAYtB,MACxC8B,QAASR,YAAYQ,cAIjCV,kBAAkBb,MAAK,CAACC,EAAGC,IAAOD,EAAER,KAAK+B,cAActB,EAAET,QAClDoB,kBAGXY,gBAAgBvC,WACL,oBAASX,KAAKuB,WAAYZ,IAGrCwC,cAAcxC,WACHX,KAAKE,YAAY6C,IAAIpC,IAShCyC,WAAWC,cACDC,QAAU,GAGJD,OAAOE,UAAUC,oBAC7BC,OAAOC,OAAO1D,KAAKK,YAAYU,SAAQsB,YACnCiB,QAAQrC,KAAK,CACTN,GAAI0B,UAAU1B,GACdO,KAAMmB,UAAUlB,YAChBc,KAAMI,UAAUxB,aAChB8B,WAAY,YAAcN,UAAUnB,KAAO,QAC3CyC,SAAUtB,UAAUuB,KACpBnD,SAAUT,KAAKoC,qBAAqBC,WACpCwB,WAAYxB,UAAU9B,QAAQuD,KAAK,KACnCtC,SAAUa,UAAUxB,aACpBO,aAAciB,UAAUjB,kBAGhCkC,QAAQ7B,MAAK,CAACC,EAAGC,IAAMD,EAAEN,aAAeO,EAAEP,eAEnCkC,QASXS,mBAAmBV,eACRI,OAAOO,OAAO,GAAI,CACrBC,UAAWZ,OAAO1C,GAClB2C,QAAStD,KAAKoD,WAAWC,QACzB9B,WAAYvB,KAAKqB,gBACjB6C,QAASlE,KAAKF,gBACdqE,UAAWnE,KAAKD,YAIxBqE,4BACWpE,KAAKF,4CASNuE,KAAO,GACPC,UAAY,qBAEbjE,WAAWU,SAAQwD,cAEhBA,QAAQX,KAAKY,SAASF,YAAYvD,SAAQ0D,WACR,IAA9BJ,KAAKK,QAAQD,QAAQ,KACrBJ,KAAKpD,KAAKwD,QAAQ,WAKtBF,QAAQI,KAAKH,SAASF,YAAYvD,SAAQ0D,WACR,IAA9BJ,KAAKK,QAAQD,QAAQ,KACrBJ,KAAKpD,KAAKwD,QAAQ,gBAKxBG,mBAAqB,oBAAWP,KAAKQ,KAAKC,OAAUA,IAAAA,IAAKC,WAAAA,8BACxD,IAAIC,IAAIX,KAAKQ,KAAI,CAACC,IAAKG,QAAW,CAACH,IAAKF,aAAaK,0CAItDC,WAAa,cAAU,CAAC,CAC1BC,WAAY,kCACZC,KAAM,CACFC,UAAWrF,KAAKH,YAChBD,UAAWI,KAAKJ,cAEpB,GAAG0F,OAAMC,mBACLC,MAAMD,IAAIE,YAGZC,kBAAoB,GAC1BR,KAAK7E,WAAWU,SAAQsB,YACpBqD,kBAAkBrD,UAAU1B,IAAM0B,mBAGhCsD,gBAAkB,GACxBT,KAAKzE,SAASM,SAAQwB,UAClBoD,gBAAgBpD,QAAQ5B,IAAM4B,iBAG5BqD,kBAAoB,GAC1BV,KAAK3D,WAAWR,SAAQS,WACpBoE,kBAAkBpE,SAASb,IAAMa,iBAGhCnB,WAAaqF,uBACbjF,SAAWkF,qBACXpE,WAAaqE,uBACbrF,QAAU2E,KAAK3E"} \ No newline at end of file diff --git a/amd/build/helper.min.js b/amd/build/helper.min.js new file mode 100644 index 0000000..2fc63c6 --- /dev/null +++ b/amd/build/helper.min.js @@ -0,0 +1,3 @@ +define("tiny_elements/helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.findByName=_exports.findById=void 0;_exports.findById=(set,id)=>set.find((element=>void 0!==element&&element.id==id));_exports.findByName=(set,name)=>set.find((element=>void 0!==element&&element.name==name))})); + +//# sourceMappingURL=helper.min.js.map \ No newline at end of file diff --git a/amd/build/helper.min.js.map b/amd/build/helper.min.js.map new file mode 100644 index 0000000..595a01d --- /dev/null +++ b/amd/build/helper.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Some helper functions for tiny_elements.\n *\n * @module tiny_elements/helper\n * @copyright 2024 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const findById = (set, id) => {\n return set.find((element) => element !== undefined && element.id == id);\n};\n\nexport const findByName = (set, name) => {\n return set.find((element) => element !== undefined && element.name == name);\n};\n"],"names":["set","id","find","element","undefined","name"],"mappings":"qLAwBwB,CAACA,IAAKC,KACnBD,IAAIE,MAAMC,cAAwBC,IAAZD,SAAyBA,QAAQF,IAAMA,yBAG9C,CAACD,IAAKK,OACrBL,IAAIE,MAAMC,cAAwBC,IAAZD,SAAyBA,QAAQE,MAAQA"} \ No newline at end of file diff --git a/amd/build/imagepicker.min.js b/amd/build/imagepicker.min.js new file mode 100644 index 0000000..739450b --- /dev/null +++ b/amd/build/imagepicker.min.js @@ -0,0 +1,11 @@ +define("tiny_elements/imagepicker",["exports","core/modal","core/ajax","core/templates","core/str"],(function(_exports,_modal,_ajax,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Choose from images for iconurls. + * + * @module tiny_elements/imagepicker + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=_interopRequireDefault(_modal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates);_exports.init=async(clickSelector,targetSelector)=>{document.querySelectorAll(clickSelector).forEach((element=>{let targetElement=element.closest("fieldset, form").querySelector(targetSelector);element.addEventListener("click",(async()=>{let categoryid=0;if(element.dataset.categoryid)categoryid=element.dataset.categoryid;else{let categoryidelement=element.closest("form").querySelector('[name="compcat"], [name="categoryid"]');categoryidelement&&(categoryid=categoryidelement.value)}let categoryname="";if(element.dataset.categoryname)categoryname=element.dataset.categoryname;else{let categoryelement=element.closest("form").querySelector('[name="categoryname"]');categoryelement&&(categoryname=categoryelement.value)}const result=await _ajax.default.call([{methodname:"tiny_elements_get_images",args:{contextid:1,categoryid:categoryid,categoryname:categoryname}}])[0],renderedTemplate=await _templates.default.render("tiny_elements/imagepicker",{images:result}),pickerModal=await _modal.default.create({removeOnClose:!0,large:!0,body:renderedTemplate,returnElement:element,title:(0,_str.getString)("showprinturls","tiny_elements")});pickerModal.show();pickerModal.getRoot()[0].querySelectorAll(".tiny_elements_thumbnail").forEach((thumbnail=>{thumbnail.addEventListener("click",(event=>{const url=event.target.closest("img").src;targetElement.value=url,pickerModal.hide()}))}))}))}))}})); + +//# sourceMappingURL=imagepicker.min.js.map \ No newline at end of file diff --git a/amd/build/imagepicker.min.js.map b/amd/build/imagepicker.min.js.map new file mode 100644 index 0000000..1b9f480 --- /dev/null +++ b/amd/build/imagepicker.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"imagepicker.min.js","sources":["../src/imagepicker.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Choose from images for iconurls.\n *\n * @module tiny_elements/imagepicker\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport Ajax from 'core/ajax';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\n\nexport const init = async(clickSelector, targetSelector) => {\n const clickTargets = document.querySelectorAll(clickSelector);\n\n clickTargets.forEach((element) => {\n let targetElement = element.closest('fieldset, form').querySelector(targetSelector);\n\n element.addEventListener('click', async() => {\n // Let's see if we can figure out the category.\n let categoryid = 0;\n if (element.dataset.categoryid) {\n categoryid = element.dataset.categoryid;\n } else {\n let categoryidelement = element.closest('form').querySelector('[name=\"compcat\"], [name=\"categoryid\"]');\n if (categoryidelement) {\n categoryid = categoryidelement.value;\n }\n }\n\n let categoryname = '';\n if (element.dataset.categoryname) {\n categoryname = element.dataset.categoryname;\n } else {\n let categoryelement = element.closest('form').querySelector('[name=\"categoryname\"]');\n if (categoryelement) {\n categoryname = categoryelement.value;\n }\n }\n\n const result = await Ajax.call([{\n methodname: 'tiny_elements_get_images',\n args: {\n contextid: 1,\n categoryid: categoryid,\n categoryname: categoryname,\n },\n }])[0];\n\n const renderedTemplate = await Templates.render('tiny_elements/imagepicker', {\n images: result,\n });\n\n const pickerModal = await Modal.create({\n removeOnClose: true,\n large: true,\n body: renderedTemplate,\n returnElement: element,\n title: getString('showprinturls', 'tiny_elements'),\n });\n\n pickerModal.show();\n\n const root = pickerModal.getRoot()[0];\n root.querySelectorAll('.tiny_elements_thumbnail').forEach((thumbnail) => {\n thumbnail.addEventListener('click', (event) => {\n const image = event.target.closest('img');\n const url = image.src;\n targetElement.value = url;\n pickerModal.hide();\n });\n });\n });\n });\n};\n"],"names":["async","clickSelector","targetSelector","document","querySelectorAll","forEach","element","targetElement","closest","querySelector","addEventListener","categoryid","dataset","categoryidelement","value","categoryname","categoryelement","result","Ajax","call","methodname","args","contextid","renderedTemplate","Templates","render","images","pickerModal","Modal","create","removeOnClose","large","body","returnElement","title","show","getRoot","thumbnail","event","url","target","src","hide"],"mappings":";;;;;;;;wNA6BoBA,MAAMC,cAAeC,kBAChBC,SAASC,iBAAiBH,eAElCI,SAASC,cACdC,cAAgBD,QAAQE,QAAQ,kBAAkBC,cAAcP,gBAEpEI,QAAQI,iBAAiB,SAASV,cAE1BW,WAAa,KACbL,QAAQM,QAAQD,WAChBA,WAAaL,QAAQM,QAAQD,eAC1B,KACCE,kBAAoBP,QAAQE,QAAQ,QAAQC,cAAc,yCAC1DI,oBACAF,WAAaE,kBAAkBC,WAInCC,aAAe,MACfT,QAAQM,QAAQG,aAChBA,aAAeT,QAAQM,QAAQG,iBAC5B,KACCC,gBAAkBV,QAAQE,QAAQ,QAAQC,cAAc,yBACxDO,kBACAD,aAAeC,gBAAgBF,aAIjCG,aAAeC,cAAKC,KAAK,CAAC,CAC5BC,WAAY,2BACZC,KAAM,CACFC,UAAW,EACXX,WAAYA,WACZI,aAAcA,iBAElB,GAEEQ,uBAAyBC,mBAAUC,OAAO,4BAA6B,CACzEC,OAAQT,SAGNU,kBAAoBC,eAAMC,OAAO,CACnCC,eAAe,EACfC,OAAO,EACPC,KAAMT,iBACNU,cAAe3B,QACf4B,OAAO,kBAAU,gBAAiB,mBAGtCP,YAAYQ,OAECR,YAAYS,UAAU,GAC9BhC,iBAAiB,4BAA4BC,SAASgC,YACvDA,UAAU3B,iBAAiB,SAAU4B,cAE3BC,IADQD,MAAME,OAAOhC,QAAQ,OACjBiC,IAClBlC,cAAcO,MAAQyB,IACtBZ,YAAYe"} \ No newline at end of file diff --git a/amd/build/management.min.js b/amd/build/management.min.js new file mode 100644 index 0000000..0ecd1fb --- /dev/null +++ b/amd/build/management.min.js @@ -0,0 +1,11 @@ +define("tiny_elements/management",["exports","tiny_elements/previewmodal","core_form/modalform","core/notification","core/str","core/ajax","core/templates","core/log"],(function(_exports,_previewmodal,_modalform,_notification,_str,_ajax,_templates,_log){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Management functions for tiny_elements admin backend. + * + * @module tiny_elements/management + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.wipe=_exports.init=_exports.duplicateItem=_exports.deleteItem=void 0,_modalform=_interopRequireDefault(_modalform),_notification=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(_notification),_log=_interopRequireDefault(_log);_exports.init=async params=>{document.getElementById("elements_import").addEventListener("click",(async e=>{importModal(e)})),document.getElementsByClassName("add").forEach((element=>{element.addEventListener("click",(async e=>{showModal(e,element.dataset.id,element.dataset.table)}))})),document.getElementsByClassName("edit").forEach((element=>{element.addEventListener("click",(async e=>{showModal(e,element.dataset.id,element.dataset.table)}))})),document.getElementsByClassName("delete").forEach((element=>{element.addEventListener("click",(async e=>{deleteModal(e,element.dataset.id,element.dataset.title,element.dataset.table)}))})),document.getElementsByClassName("preview-button").forEach((element=>{element.addEventListener("click",(async e=>{previewModal(e)}))})),document.getElementsByClassName("compcat").forEach((element=>{element.addEventListener("click",(async e=>{showItems(e,element.dataset.compcat)}))})),document.getElementsByClassName("editlicenses").forEach((element=>{element.addEventListener("click",(async e=>{editlicensesModal(e,element.dataset.id)}))})),document.querySelectorAll(".buttonicons").forEach((element=>{element.addEventListener("click",(async e=>{compflavorModal(e)}))})),document.getElementById("elements_displaynames_button").addEventListener("click",(async e=>{displaynamesModal(e)})),document.getElementById("elements_displaynames_flavor_button").addEventListener("click",(async e=>{displaynamesFlavorModal(e)})),document.getElementById("elements_displaynames_variant_button").addEventListener("click",(async e=>{displaynamesVariantModal(e)})),document.getElementsByClassName("duplicate").forEach((element=>{element.addEventListener("click",(async()=>{duplicateItem(element.dataset.id,element.dataset.table).always((()=>reload()))}))}));let wipebutton=document.getElementById("elements_wipe");if(wipebutton&&wipebutton.addEventListener("click",(async e=>{wipeModal(e)})),document.querySelectorAll(".flavor .card-body > .clickingextended, .component .card-body > .clickingextended, .variant .card-body > .clickingextended").forEach((element=>{element.addEventListener("click",(async e=>{e.target.closest(".item").querySelector("a.edit").click()}))})),params.compcatactive){let compcat=document.querySelector('.compcat[data-compcat="'+params.compcatactive+'"]');compcat&&(showItems(!1,params.compcatactive),compcat.classList.add("active"))}};const showModal=async(event,id,table)=>{let title;event.preventDefault(),title=0==id?(0,_str.get_string)("additem","tiny_elements"):(0,_str.get_string)("edititem","tiny_elements");const modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_"+table+"_form",args:{id:id,compcat:getActiveCompcatId(),categoryname:getActiveCompcatName()},modalConfig:{title:title},returnFocus:event.target});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>reload())),await modalForm.show()},previewModal=async event=>{event.preventDefault();let preview=event.target.closest(".preview-button");const modal=await _previewmodal.PreviewModal.create({templateContext:{component:preview.dataset.component,flavors:preview.dataset.flavors.trim().split(" "),config:M.cfg}});await modal.show()},importModal=async event=>{event.preventDefault();let title=(0,_str.get_string)("import","tiny_elements");const modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_import_form",args:{},modalConfig:{title:title}});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,importModalSubmitted),await modalForm.show()},importModalSubmitted=async event=>{event.detail.update?location.reload():(event.stopPropagation(),(0,_templates.render)("tiny_elements/management_import_form_result",event.detail).then((async html=>(await _notification.default.alert((0,_str.get_string)("import_simulation","tiny_elements"),html,(0,_str.get_string)("close","tiny_elements")),!0))).catch((error=>{(0,_notification.exception)(error)})))},compflavorModal=async event=>{var _target$dataset$compo,_target$dataset$flavo;event.preventDefault();let title=(0,_str.get_string)("manage","tiny_elements");const target=event.target.closest(".buttonicons"),component=null!==(_target$dataset$compo=target.dataset.component)&&void 0!==_target$dataset$compo?_target$dataset$compo:"",flavor=null!==(_target$dataset$flavo=target.dataset.flavor)&&void 0!==_target$dataset$flavo?_target$dataset$flavo:"",modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_comp_flavor_form",args:{component:component,flavor:flavor},modalConfig:{title:title}});await modalForm.show()},editlicensesModal=async(event,id)=>{event.preventDefault();let title=(0,_str.get_string)("editlicenses","tiny_elements");const modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_editlicense_form",args:{id:id},modalConfig:{title:title}});await modalForm.show()},displaynamesModal=async event=>{event.preventDefault();let title=(0,_str.get_string)("manage","tiny_elements");const modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_displaynames_form",args:{},modalConfig:{title:title}});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>location.reload())),await modalForm.show()},displaynamesFlavorModal=async event=>{event.preventDefault();let title=(0,_str.get_string)("manage","tiny_elements");const modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_displaynames_flavors_form",args:{},modalConfig:{title:title}});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>location.reload())),await modalForm.show()},displaynamesVariantModal=async event=>{event.preventDefault();let title=(0,_str.get_string)("manage","tiny_elements");const modalForm=new _modalform.default({formClass:"tiny_elements\\form\\management_displaynames_variants_form",args:{},modalConfig:{title:title}});modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>location.reload())),await modalForm.show()},deleteModal=(event,id,title,table)=>{event.preventDefault(),(0,_notification.deleteCancelPromise)((0,_str.get_string)("delete","tiny_elements",title),(0,_str.get_string)("deletewarning","tiny_elements")).then((async()=>{if(0!==id)try{if(await deleteItem(id,table)){const link=document.querySelector('[data-table="'+table+'"][data-id="'+id+'"]');if(link){link.closest(".item").remove()}}}catch(error){(0,_notification.exception)(error)}})).catch((err=>{err.message&&_log.default.error(err.message)}))},wipeModal=event=>{event.preventDefault(),(0,_notification.deleteCancelPromise)((0,_str.get_string)("wipe","tiny_elements"),(0,_str.get_string)("wipewarning","tiny_elements")).then((async()=>{try{await wipe(),reload()}catch(error){(0,_notification.exception)(error)}})).catch((err=>{err.message&&_log.default.error(err.message)}))},deleteItem=(id,table)=>(0,_ajax.call)([{methodname:"tiny_elements_delete_item",args:{id:id,table:table}}])[0];_exports.deleteItem=deleteItem;const wipe=()=>(0,_ajax.call)([{methodname:"tiny_elements_wipe",args:{contextid:1}}])[0];_exports.wipe=wipe;const showItems=(event,compcat)=>{document.querySelectorAll(".flavor, .component, .variant").forEach((element=>{element.classList.add("hidden")}));let itemsShow=document.querySelectorAll('[data-categoryname="'+compcat+'"]'),usedFlavors=[];itemsShow.forEach((element=>{if(element.classList.remove("hidden"),void 0!==element.dataset.flavors){let flavors=element.dataset.flavors.split(" ");for(let value of flavors)usedFlavors.includes(value)||0==value.length||usedFlavors.push(value)}}));let flavorstring=usedFlavors.map((item=>".".concat(item))).join(", ");if(flavorstring.length){document.querySelectorAll(flavorstring).forEach((element=>{element.classList.remove("hidden")}))}if(document.getElementsByClassName("addcontainer").forEach((element=>{element.classList.remove("hidden")})),event){document.getElementsByClassName("compcat").forEach((element=>{element.classList.remove("active")})),event.target.closest(".compcat").classList.add("active")}if("found-items"==compcat){let found=document.querySelector('.compcat[data-compcat="found-items"]');if(found.dataset.loneflavors.length){document.querySelectorAll(found.dataset.loneflavors).forEach((element=>{element.classList.remove("hidden")}))}if(found.dataset.lonevariants.length){document.querySelectorAll(found.dataset.lonevariants).forEach((element=>{element.classList.remove("hidden")}))}if(found.dataset.lonecomponents.length){document.querySelectorAll(found.dataset.lonecomponents).forEach((element=>{element.classList.remove("hidden")}))}}},reload=()=>{const currentUrl=new URL(window.location.href);currentUrl.searchParams.set("compcat",getActiveCompcatName()),window.location.href=currentUrl.toString(),window.location.reload()},getActiveCompcatName=()=>{var _compcat$dataset$comp;const compcat=document.querySelector(".compcat.active");return compcat&&null!==(_compcat$dataset$comp=compcat.dataset.compcat)&&void 0!==_compcat$dataset$comp?_compcat$dataset$comp:""},getActiveCompcatId=()=>{var _compcat$dataset$id;const compcat=document.querySelector(".compcat.active");return compcat&&null!==(_compcat$dataset$id=compcat.dataset.id)&&void 0!==_compcat$dataset$id?_compcat$dataset$id:0},duplicateItem=(id,table)=>(0,_ajax.call)([{methodname:"tiny_elements_duplicate_item",args:{id:id,table:table}}])[0];_exports.duplicateItem=duplicateItem})); + +//# sourceMappingURL=management.min.js.map \ No newline at end of file diff --git a/amd/build/management.min.js.map b/amd/build/management.min.js.map new file mode 100644 index 0000000..13c9473 --- /dev/null +++ b/amd/build/management.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"management.min.js","sources":["../src/management.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Management functions for tiny_elements admin backend.\n *\n * @module tiny_elements/management\n * @copyright 2024 ISB Bayern\n * @author Tobias Garske\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {PreviewModal} from 'tiny_elements/previewmodal';\nimport ModalForm from 'core_form/modalform';\nimport Notification from 'core/notification';\nimport {get_string as getString} from 'core/str';\nimport {exception as displayException, deleteCancelPromise} from 'core/notification';\nimport {call as fetchMany} from 'core/ajax';\nimport {render as renderTemplate} from 'core/templates';\nimport Log from 'core/log';\n\nexport const init = async(params) => {\n\n // Add listener to import xml files.\n let importxml = document.getElementById('elements_import');\n importxml.addEventListener('click', async(e) => {\n importModal(e);\n });\n\n // Add listener for adding a new item.\n let additem = document.getElementsByClassName('add');\n additem.forEach(element => {\n element.addEventListener('click', async(e) => {\n showModal(e, element.dataset.id, element.dataset.table);\n });\n });\n\n // Add listener to edit items.\n let edititems = document.getElementsByClassName('edit');\n edititems.forEach(element => {\n element.addEventListener('click', async(e) => {\n showModal(e, element.dataset.id, element.dataset.table);\n });\n });\n\n // Add listener to delete items.\n let deleteitems = document.getElementsByClassName('delete');\n deleteitems.forEach(element => {\n element.addEventListener('click', async(e) => {\n deleteModal(e, element.dataset.id, element.dataset.title, element.dataset.table);\n });\n });\n\n // Add listener to preview items.\n let previewitems = document.getElementsByClassName('preview-button');\n previewitems.forEach(element => {\n element.addEventListener('click', async(e) => {\n previewModal(e);\n });\n });\n\n // Add listener to select compcat to show corresponding items.\n let compcats = document.getElementsByClassName('compcat');\n compcats.forEach(element => {\n element.addEventListener('click', async(e) => {\n showItems(e, element.dataset.compcat);\n });\n });\n\n // Add listener to edit licenses icon.\n let editlicenses = document.getElementsByClassName('editlicenses');\n editlicenses.forEach(element => {\n element.addEventListener('click', async(e) => {\n editlicensesModal(e, element.dataset.id);\n });\n });\n\n // Add listener to manage component flavor relation.\n let buttonicons = document.querySelectorAll('.buttonicons');\n buttonicons.forEach(element => {\n element.addEventListener('click', async(e) => {\n compflavorModal(e);\n });\n });\n\n let displaynamesbutton = document.getElementById('elements_displaynames_button');\n displaynamesbutton.addEventListener('click', async(e) => {\n displaynamesModal(e);\n });\n\n let displaynamesflavorbutton = document.getElementById('elements_displaynames_flavor_button');\n displaynamesflavorbutton.addEventListener('click', async(e) => {\n displaynamesFlavorModal(e);\n });\n\n let displaynamesvariantbutton = document.getElementById('elements_displaynames_variant_button');\n displaynamesvariantbutton.addEventListener('click', async(e) => {\n displaynamesVariantModal(e);\n });\n\n // Add listener to duplicate items.\n let duplicateitems = document.getElementsByClassName('duplicate');\n duplicateitems.forEach(element => {\n element.addEventListener('click', async() => {\n duplicateItem(element.dataset.id, element.dataset.table).always(() => reload());\n });\n });\n\n // Add listener to wipe all items.\n let wipebutton = document.getElementById('elements_wipe');\n if (wipebutton) {\n wipebutton.addEventListener('click', async(e) => {\n wipeModal(e);\n });\n }\n\n // Add image and text to item setting click area.\n let enlargeItems = document.querySelectorAll(\n '.flavor .card-body > .clickingextended, .component .card-body > .clickingextended, .variant .card-body > .clickingextended'\n );\n enlargeItems.forEach(element => {\n element.addEventListener('click', async(e) => {\n let item = e.target.closest('.item');\n item.querySelector('a.edit').click();\n });\n });\n\n // After submitting a new item, reset active compcat.\n if (params.compcatactive) {\n let compcat = document.querySelector('.compcat[data-compcat=\"' + params.compcatactive + '\"]');\n if (compcat) {\n showItems(false, params.compcatactive);\n compcat.classList.add('active');\n }\n }\n};\n\n/**\n * Show dynamic form to add/edit a source.\n * @param {*} event\n * @param {*} id\n * @param {*} table\n */\nconst showModal = async(event, id, table) => {\n event.preventDefault();\n let title;\n if (id == 0) {\n title = getString('additem', 'tiny_elements');\n } else {\n title = getString('edititem', 'tiny_elements');\n }\n\n const modalForm = new ModalForm({\n // Set formclass, depending on component.\n formClass: \"tiny_elements\\\\form\\\\management_\" + table + \"_form\",\n args: {\n id: id,\n compcat: getActiveCompcatId(),\n categoryname: getActiveCompcatName(),\n },\n modalConfig: {title: title},\n returnFocus: event.target,\n });\n // Conditional reload page after submit.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => reload());\n\n await modalForm.show();\n};\n\n/**\n * Show modal to preview css version.\n * @param {*} event\n */\nconst previewModal = async(event) => {\n event.preventDefault();\n let preview = event.target.closest(\".preview-button\");\n const modal = await PreviewModal.create({\n templateContext: {\n component: preview.dataset.component,\n flavors: preview.dataset.flavors.trim().split(\" \"),\n config: M.cfg,\n },\n });\n await modal.show();\n};\n\n/**\n * Show dynamic form to import xml backups.\n * @param {*} event\n */\nconst importModal = async(event) => {\n event.preventDefault();\n let title = getString('import', 'tiny_elements');\n\n const modalForm = new ModalForm({\n // Load import form.\n formClass: \"tiny_elements\\\\form\\\\management_import_form\",\n args: {},\n modalConfig: {title: title},\n });\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, importModalSubmitted);\n\n await modalForm.show();\n};\n\n/**\n * Process import form submit.\n * @param {*} event\n */\nconst importModalSubmitted = async(event) => {\n // Reload page after submit.\n if (event.detail.update) {\n location.reload();\n } else {\n event.stopPropagation();\n renderTemplate('tiny_elements/management_import_form_result', event.detail).then(async(html) => {\n await Notification.alert(\n getString('import_simulation', 'tiny_elements'),\n html,\n getString('close', 'tiny_elements')\n );\n return true;\n }).catch((error) => {\n displayException(error);\n });\n }\n};\n\n/**\n * Load modal to edit icon urls.\n * @param {*} event\n */\nconst compflavorModal = async(event) => {\n event.preventDefault();\n let title = getString('manage', 'tiny_elements');\n const target = event.target.closest('.buttonicons');\n const component = target.dataset.component ?? '';\n const flavor = target.dataset.flavor ?? '';\n const modalForm = new ModalForm({\n // Load import form.\n formClass: \"tiny_elements\\\\form\\\\management_comp_flavor_form\",\n args: {\n component: component,\n flavor: flavor,\n },\n modalConfig: {title: title},\n });\n\n await modalForm.show();\n};\n\n/**\n * Load modal to edit licenses of icons.\n * @param {*} event\n * @param {*} id\n */\nconst editlicensesModal = async(event, id) => {\n event.preventDefault();\n let title = getString('editlicenses', 'tiny_elements');\n const modalForm = new ModalForm({\n formClass: \"tiny_elements\\\\form\\\\management_editlicense_form\",\n args: {\n id: id,\n },\n modalConfig: {title: title},\n });\n await modalForm.show();\n};\n\n/**\n * Load modal to edit displaynames.\n * @param {*} event\n * @returns {void}\n */\nconst displaynamesModal = async(event) => {\n event.preventDefault();\n let title = getString('manage', 'tiny_elements');\n\n const modalForm = new ModalForm({\n // Load displaynames bulk edit form.\n formClass: \"tiny_elements\\\\form\\\\management_displaynames_form\",\n args: {},\n modalConfig: {title: title},\n });\n\n // Reload page after submit.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => location.reload());\n\n await modalForm.show();\n};\n\n/**\n * Load modal to edit displaynames.\n * @param {*} event\n * @returns {void}\n */\nconst displaynamesFlavorModal = async(event) => {\n event.preventDefault();\n let title = getString('manage', 'tiny_elements');\n\n const modalForm = new ModalForm({\n // Load displaynames bulk edit form.\n formClass: \"tiny_elements\\\\form\\\\management_displaynames_flavors_form\",\n args: {},\n modalConfig: {title: title},\n });\n\n // Reload page after submit.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => location.reload());\n\n await modalForm.show();\n};\n\n/**\n * Load modal to edit displaynames.\n * @param {*} event\n * @returns {void}\n */\nconst displaynamesVariantModal = async(event) => {\n event.preventDefault();\n let title = getString('manage', 'tiny_elements');\n\n const modalForm = new ModalForm({\n // Load displaynames bulk edit form.\n formClass: \"tiny_elements\\\\form\\\\management_displaynames_variants_form\",\n args: {},\n modalConfig: {title: title},\n });\n\n // Reload page after submit.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => location.reload());\n\n await modalForm.show();\n};\n\n/**\n * Show dynamic form to delete a source.\n * @param {*} event\n * @param {*} id\n * @param {*} title\n * @param {*} table\n */\nconst deleteModal = (event, id, title, table) => {\n event.preventDefault();\n\n deleteCancelPromise(\n getString('delete', 'tiny_elements', title),\n getString('deletewarning', 'tiny_elements'),\n ).then(async() => {\n if (id !== 0) {\n try {\n const deleted = await deleteItem(id, table);\n if (deleted) {\n const link = document.querySelector('[data-table=\"' + table + '\"][data-id=\"' + id + '\"]');\n if (link) {\n const card = link.closest(\".item\");\n card.remove();\n }\n }\n } catch (error) {\n displayException(error);\n }\n }\n return;\n }).catch((err) => {\n if (err.message) {\n Log.error(err.message);\n }\n return;\n });\n};\n\n/**\n * Show a modal to confirm wiping all items.\n * @param {*} event\n */\nconst wipeModal = (event) => {\n event.preventDefault();\n\n deleteCancelPromise(\n getString('wipe', 'tiny_elements'),\n getString('wipewarning', 'tiny_elements')\n ).then(async() => {\n try {\n await wipe();\n reload();\n } catch (error) {\n displayException(error);\n }\n return;\n }).catch((err) => {\n if (err.message) {\n Log.error(err.message);\n }\n return;\n });\n};\n\n/**\n * Delete elements items.\n * @param {*} id\n * @param {*} table\n * @returns {mixed}\n */\nexport const deleteItem = (\n id,\n table,\n) => fetchMany(\n [{\n methodname: 'tiny_elements_delete_item',\n args: {\n id,\n table,\n }\n }])[0];\n\n/**\n * Wipe all elements items.\n * @returns {mixed}\n */\nexport const wipe = () => fetchMany(\n [{\n methodname: 'tiny_elements_wipe',\n args: {\n \"contextid\": 1,\n }\n }])[0];\n\n/**\n * Show items after clicking a compcat.\n * @param {*} event\n * @param {*} compcat\n */\nconst showItems = (event, compcat) => {\n // But first hide all items.\n let itemsHide = document.querySelectorAll('.flavor, .component, .variant');\n itemsHide.forEach(element => {\n element.classList.add('hidden');\n });\n\n // Show component and variants with compcat name and read the flavors.\n let itemsShow = document.querySelectorAll('[data-categoryname=\"' + compcat + '\"]');\n let usedFlavors = [];\n itemsShow.forEach(element => {\n element.classList.remove('hidden');\n // Get all flavors to show if on compcat element.\n if (typeof element.dataset.flavors !== 'undefined') {\n let flavors = element.dataset.flavors.split(' ');\n for (let value of flavors) {\n if (!usedFlavors.includes(value) && value.length != 0) {\n usedFlavors.push(value);\n }\n }\n }\n });\n\n // Show the flavors.\n let flavorstring = usedFlavors.map(item => `.${item}`).join(', ');\n if (flavorstring.length) {\n let flavorsShow = document.querySelectorAll(flavorstring);\n flavorsShow.forEach(element => {\n element.classList.remove('hidden');\n });\n }\n\n // Show add buttons.\n let addsShow = document.getElementsByClassName('addcontainer');\n addsShow.forEach(element => {\n element.classList.remove('hidden');\n });\n\n // Unmark all and mark clicked compcat.\n if (event) {\n let items = document.getElementsByClassName('compcat');\n items.forEach(element => {\n element.classList.remove('active');\n });\n let item = event.target.closest('.compcat');\n item.classList.add('active');\n }\n\n // Special case, unassigned items, show all items without connection to compcat.\n if (compcat == 'found-items') {\n let found = document.querySelector('.compcat[data-compcat=\"found-items\"]');\n if (found.dataset.loneflavors.length) {\n let flavorsShow = document.querySelectorAll(found.dataset.loneflavors);\n flavorsShow.forEach(element => {\n element.classList.remove('hidden');\n });\n }\n if (found.dataset.lonevariants.length) {\n let variantsShow = document.querySelectorAll(found.dataset.lonevariants);\n variantsShow.forEach(element => {\n element.classList.remove('hidden');\n });\n }\n if (found.dataset.lonecomponents.length) {\n let componentsShow = document.querySelectorAll(found.dataset.lonecomponents);\n componentsShow.forEach(element => {\n element.classList.remove('hidden');\n });\n }\n }\n};\n\n/**\n * Reload page with active compcat.\n */\nconst reload = () => {\n // Reload page with active compcat.\n const currentUrl = new URL(window.location.href);\n currentUrl.searchParams.set('compcat', getActiveCompcatName());\n window.location.href = currentUrl.toString();\n window.location.reload();\n};\n\n/**\n * Get the current active compcat.\n * @returns string Name of active compcat.\n */\nconst getActiveCompcatName = () => {\n const compcat = document.querySelector('.compcat.active');\n if (!compcat) {\n return '';\n }\n return compcat.dataset.compcat ?? '';\n};\n\n/**\n * Get the current active compcat.\n * @returns int Id of active compcat.\n */\nconst getActiveCompcatId = () => {\n const compcat = document.querySelector('.compcat.active');\n if (!compcat) {\n return 0;\n }\n return compcat.dataset.id ?? 0;\n};\n\n/**\n * Duplicate elements items.\n * @param {*} id\n * @param {*} table\n * @returns {mixed}\n */\nexport const duplicateItem = (id, table) => fetchMany(\n [{\n methodname: 'tiny_elements_duplicate_item',\n args: {\n id,\n table,\n }\n }])[0];\n"],"names":["async","document","getElementById","addEventListener","importModal","e","getElementsByClassName","forEach","element","showModal","dataset","id","table","deleteModal","title","previewModal","showItems","compcat","editlicensesModal","querySelectorAll","compflavorModal","displaynamesModal","displaynamesFlavorModal","displaynamesVariantModal","duplicateItem","always","reload","wipebutton","wipeModal","target","closest","querySelector","click","params","compcatactive","classList","add","event","preventDefault","modalForm","ModalForm","formClass","args","getActiveCompcatId","categoryname","getActiveCompcatName","modalConfig","returnFocus","events","FORM_SUBMITTED","show","preview","modal","PreviewModal","create","templateContext","component","flavors","trim","split","config","M","cfg","importModalSubmitted","detail","update","location","stopPropagation","then","Notification","alert","html","catch","error","flavor","deleteItem","link","remove","err","message","wipe","methodname","itemsShow","usedFlavors","value","includes","length","push","flavorstring","map","item","join","found","loneflavors","lonevariants","lonecomponents","currentUrl","URL","window","href","searchParams","set","toString"],"mappings":";;;;;;;;m5BAiCoBA,MAAAA,SAGAC,SAASC,eAAe,mBAC9BC,iBAAiB,SAASH,MAAAA,IAChCI,YAAYC,MAIFJ,SAASK,uBAAuB,OACtCC,SAAQC,UACZA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9BS,UAAUJ,EAAGG,QAAQE,QAAQC,GAAIH,QAAQE,QAAQE,aAKzCX,SAASK,uBAAuB,QACtCC,SAAQC,UACdA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9BS,UAAUJ,EAAGG,QAAQE,QAAQC,GAAIH,QAAQE,QAAQE,aAKvCX,SAASK,uBAAuB,UACtCC,SAAQC,UAChBA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9Ba,YAAYR,EAAGG,QAAQE,QAAQC,GAAIH,QAAQE,QAAQI,MAAON,QAAQE,QAAQE,aAK/DX,SAASK,uBAAuB,kBACtCC,SAAQC,UACjBA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9Be,aAAaV,SAKNJ,SAASK,uBAAuB,WACtCC,SAAQC,UACbA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9BgB,UAAUX,EAAGG,QAAQE,QAAQO,eAKlBhB,SAASK,uBAAuB,gBACtCC,SAAQC,UACjBA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9BkB,kBAAkBb,EAAGG,QAAQE,QAAQC,UAK3BV,SAASkB,iBAAiB,gBAChCZ,SAAQC,UAChBA,QAAQL,iBAAiB,SAASH,MAAAA,IAC9BoB,gBAAgBf,SAICJ,SAASC,eAAe,gCAC9BC,iBAAiB,SAASH,MAAAA,IACzCqB,kBAAkBhB,MAGSJ,SAASC,eAAe,uCAC9BC,iBAAiB,SAASH,MAAAA,IAC/CsB,wBAAwBjB,MAGIJ,SAASC,eAAe,wCAC9BC,iBAAiB,SAASH,MAAAA,IAChDuB,yBAAyBlB,MAIRJ,SAASK,uBAAuB,aACtCC,SAAQC,UACnBA,QAAQL,iBAAiB,SAASH,UAC9BwB,cAAchB,QAAQE,QAAQC,GAAIH,QAAQE,QAAQE,OAAOa,QAAO,IAAMC,qBAK1EC,WAAa1B,SAASC,eAAe,oBACrCyB,YACAA,WAAWxB,iBAAiB,SAASH,MAAAA,IACjC4B,UAAUvB,MAKCJ,SAASkB,iBACxB,8HAESZ,SAAQC,UACjBA,QAAQL,iBAAiB,SAASH,MAAAA,IACnBK,EAAEwB,OAAOC,QAAQ,SACvBC,cAAc,UAAUC,cAKjCC,OAAOC,cAAe,KAClBjB,QAAUhB,SAAS8B,cAAc,0BAA4BE,OAAOC,cAAgB,MACpFjB,UACAD,WAAU,EAAOiB,OAAOC,eACxBjB,QAAQkB,UAAUC,IAAI,mBAW5B3B,UAAYT,MAAMqC,MAAO1B,GAAIC,aAE3BE,MADJuB,MAAMC,iBAGFxB,MADM,GAANH,IACQ,mBAAU,UAAW,kBAErB,mBAAU,WAAY,uBAG5B4B,UAAY,IAAIC,mBAAU,CAE5BC,UAAW,mCAAqC7B,MAAQ,QACxD8B,KAAM,CACF/B,GAAIA,GACJM,QAAS0B,qBACTC,aAAcC,wBAElBC,YAAa,CAAChC,MAAOA,OACrBiC,YAAaV,MAAMR,SAGvBU,UAAUpC,iBAAiBoC,UAAUS,OAAOC,gBAAgB,IAAMvB,iBAE5Da,UAAUW,QAOdnC,aAAef,MAAAA,QACjBqC,MAAMC,qBACFa,QAAUd,MAAMR,OAAOC,QAAQ,yBAC7BsB,YAAcC,2BAAaC,OAAO,CACpCC,gBAAiB,CACbC,UAAWL,QAAQzC,QAAQ8C,UAC3BC,QAASN,QAAQzC,QAAQ+C,QAAQC,OAAOC,MAAM,KAC9CC,OAAQC,EAAEC,aAGZV,MAAMF,QAOV9C,YAAcJ,MAAAA,QAChBqC,MAAMC,qBACFxB,OAAQ,mBAAU,SAAU,uBAE1ByB,UAAY,IAAIC,mBAAU,CAE5BC,UAAW,8CACXC,KAAM,GACNI,YAAa,CAAChC,MAAOA,SAEzByB,UAAUpC,iBAAiBoC,UAAUS,OAAOC,eAAgBc,4BAEtDxB,UAAUW,QAOda,qBAAuB/D,MAAAA,QAErBqC,MAAM2B,OAAOC,OACbC,SAASxC,UAETW,MAAM8B,wCACS,8CAA+C9B,MAAM2B,QAAQI,MAAKpE,MAAAA,aACvEqE,sBAAaC,OACf,mBAAU,oBAAqB,iBAC/BC,MACA,mBAAU,QAAS,mBAEhB,KACRC,OAAOC,oCACWA,YASvBrD,gBAAkBpB,MAAAA,wDACpBqC,MAAMC,qBACFxB,OAAQ,mBAAU,SAAU,uBAC1Be,OAASQ,MAAMR,OAAOC,QAAQ,gBAC9B0B,wCAAY3B,OAAOnB,QAAQ8C,iEAAa,GACxCkB,qCAAS7C,OAAOnB,QAAQgE,8DAAU,GAClCnC,UAAY,IAAIC,mBAAU,CAE5BC,UAAW,mDACXC,KAAM,CACFc,UAAWA,UACXkB,OAAQA,QAEZ5B,YAAa,CAAChC,MAAOA,eAGnByB,UAAUW,QAQdhC,kBAAoBlB,MAAMqC,MAAO1B,MACnC0B,MAAMC,qBACFxB,OAAQ,mBAAU,eAAgB,uBAChCyB,UAAY,IAAIC,mBAAU,CAC5BC,UAAW,mDACXC,KAAM,CACF/B,GAAIA,IAERmC,YAAa,CAAChC,MAAOA,eAEnByB,UAAUW,QAQd7B,kBAAoBrB,MAAAA,QACtBqC,MAAMC,qBACFxB,OAAQ,mBAAU,SAAU,uBAE1ByB,UAAY,IAAIC,mBAAU,CAE5BC,UAAW,oDACXC,KAAM,GACNI,YAAa,CAAChC,MAAOA,SAIzByB,UAAUpC,iBAAiBoC,UAAUS,OAAOC,gBAAgB,IAAMiB,SAASxC,iBAErEa,UAAUW,QAQd5B,wBAA0BtB,MAAAA,QAC5BqC,MAAMC,qBACFxB,OAAQ,mBAAU,SAAU,uBAE1ByB,UAAY,IAAIC,mBAAU,CAE5BC,UAAW,4DACXC,KAAM,GACNI,YAAa,CAAChC,MAAOA,SAIzByB,UAAUpC,iBAAiBoC,UAAUS,OAAOC,gBAAgB,IAAMiB,SAASxC,iBAErEa,UAAUW,QAQd3B,yBAA2BvB,MAAAA,QAC7BqC,MAAMC,qBACFxB,OAAQ,mBAAU,SAAU,uBAE1ByB,UAAY,IAAIC,mBAAU,CAE5BC,UAAW,6DACXC,KAAM,GACNI,YAAa,CAAChC,MAAOA,SAIzByB,UAAUpC,iBAAiBoC,UAAUS,OAAOC,gBAAgB,IAAMiB,SAASxC,iBAErEa,UAAUW,QAUdrC,YAAc,CAACwB,MAAO1B,GAAIG,MAAOF,SACnCyB,MAAMC,wDAGF,mBAAU,SAAU,gBAAiBxB,QACrC,mBAAU,gBAAiB,kBAC7BsD,MAAKpE,aACQ,IAAPW,gBAE0BgE,WAAWhE,GAAIC,OACxB,OACHgE,KAAO3E,SAAS8B,cAAc,gBAAkBnB,MAAQ,eAAiBD,GAAK,SAChFiE,KAAM,CACOA,KAAK9C,QAAQ,SACrB+C,WAGf,MAAOJ,mCACYA,WAI1BD,OAAOM,MACFA,IAAIC,sBACAN,MAAMK,IAAIC,aAUpBnD,UAAaS,QACfA,MAAMC,wDAGF,mBAAU,OAAQ,kBAClB,mBAAU,cAAe,kBAC3B8B,MAAKpE,oBAEOgF,OACNtD,SACF,MAAO+C,mCACYA,WAGtBD,OAAOM,MACFA,IAAIC,sBACAN,MAAMK,IAAIC,aAYbJ,WAAa,CACtBhE,GACAC,SACC,cACD,CAAC,CACGqE,WAAY,4BACZvC,KAAM,CACF/B,GAAAA,GACAC,MAAAA,UAEJ,wCAMKoE,KAAO,KAAM,cACtB,CAAC,CACGC,WAAY,qBACZvC,KAAM,WACW,MAEjB,4BAOF1B,UAAY,CAACqB,MAAOpB,WAENhB,SAASkB,iBAAiB,iCAChCZ,SAAQC,UACdA,QAAQ2B,UAAUC,IAAI,iBAItB8C,UAAYjF,SAASkB,iBAAiB,uBAAyBF,QAAU,MACzEkE,YAAc,GAClBD,UAAU3E,SAAQC,aACdA,QAAQ2B,UAAU0C,OAAO,eAEc,IAA5BrE,QAAQE,QAAQ+C,QAAyB,KAC5CA,QAAUjD,QAAQE,QAAQ+C,QAAQE,MAAM,SACvC,IAAIyB,SAAS3B,QACT0B,YAAYE,SAASD,QAA0B,GAAhBA,MAAME,QACtCH,YAAYI,KAAKH,eAO7BI,aAAeL,YAAYM,KAAIC,iBAAYA,QAAQC,KAAK,SACxDH,aAAaF,OAAQ,CACHrF,SAASkB,iBAAiBqE,cAChCjF,SAAQC,UAChBA,QAAQ2B,UAAU0C,OAAO,gBAKlB5E,SAASK,uBAAuB,gBACtCC,SAAQC,UACbA,QAAQ2B,UAAU0C,OAAO,aAIzBxC,MAAO,CACKpC,SAASK,uBAAuB,WACtCC,SAAQC,UACVA,QAAQ2B,UAAU0C,OAAO,aAElBxC,MAAMR,OAAOC,QAAQ,YAC3BK,UAAUC,IAAI,aAIR,eAAXnB,QAA0B,KACtB2E,MAAQ3F,SAAS8B,cAAc,2CAC/B6D,MAAMlF,QAAQmF,YAAYP,OAAQ,CAChBrF,SAASkB,iBAAiByE,MAAMlF,QAAQmF,aAC9CtF,SAAQC,UAChBA,QAAQ2B,UAAU0C,OAAO,gBAG7Be,MAAMlF,QAAQoF,aAAaR,OAAQ,CAChBrF,SAASkB,iBAAiByE,MAAMlF,QAAQoF,cAC9CvF,SAAQC,UACjBA,QAAQ2B,UAAU0C,OAAO,gBAG7Be,MAAMlF,QAAQqF,eAAeT,OAAQ,CAChBrF,SAASkB,iBAAiByE,MAAMlF,QAAQqF,gBAC9CxF,SAAQC,UACnBA,QAAQ2B,UAAU0C,OAAO,gBASnCnD,OAAS,WAELsE,WAAa,IAAIC,IAAIC,OAAOhC,SAASiC,MAC3CH,WAAWI,aAAaC,IAAI,UAAWxD,wBACvCqD,OAAOhC,SAASiC,KAAOH,WAAWM,WAClCJ,OAAOhC,SAASxC,UAOdmB,qBAAuB,qCACnB5B,QAAUhB,SAAS8B,cAAc,0BAClCd,uCAGEA,QAAQP,QAAQO,+DAFZ,IAST0B,mBAAqB,mCACjB1B,QAAUhB,SAAS8B,cAAc,0BAClCd,qCAGEA,QAAQP,QAAQC,sDAFZ,GAWFa,cAAgB,CAACb,GAAIC,SAAU,cACxC,CAAC,CACGqE,WAAY,+BACZvC,KAAM,CACF/B,GAAAA,GACAC,MAAAA,UAEJ"} \ No newline at end of file diff --git a/amd/build/modal.min.js b/amd/build/modal.min.js index 7b96d5c..b171926 100644 --- a/amd/build/modal.min.js +++ b/amd/build/modal.min.js @@ -1,10 +1,3 @@ -define("tiny_c4l/modal",["exports","core/modal","core/modal_registry"],(function(_exports,_modal,_modal_registry){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} -/** - * C4L Modal for Tiny. - * - * @module tiny_c4l/modal - * @copyright 2022 Marc Català - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);const C4LModal=class extends _modal.default{static TYPE="tiny_c4l/modal";static TEMPLATE="tiny_c4l/modal";registerEventListeners(){super.registerEventListeners()}};_modal_registry.default.register(C4LModal.TYPE,C4LModal,C4LModal.TEMPLATE);_exports.default=C4LModal;return _exports.default})); +define("tiny_elements/modal",["exports","core/modal"],(function(_exports,_modal){var obj,_class;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ElementsModal=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};const ElementsModal=(_defineProperty(_class=class extends _modal.default{configure(modalConfig){modalConfig.removeOnClose=!0,super.configure(modalConfig)}registerEventListeners(){super.registerEventListeners()}},"TYPE","tiny_elements/modal"),_defineProperty(_class,"TEMPLATE","tiny_elements/modal"),_class);_exports.ElementsModal=ElementsModal})); //# sourceMappingURL=modal.min.js.map \ No newline at end of file diff --git a/amd/build/modal.min.js.map b/amd/build/modal.min.js.map index 583ecf8..a1cb622 100644 --- a/amd/build/modal.min.js.map +++ b/amd/build/modal.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * C4L Modal for Tiny.\n *\n * @module tiny_c4l/modal\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport ModalRegistry from 'core/modal_registry';\n\nconst C4LModal = class extends Modal {\n static TYPE = 'tiny_c4l/modal';\n static TEMPLATE = 'tiny_c4l/modal';\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n }\n};\n\nModalRegistry.register(C4LModal.TYPE, C4LModal, C4LModal.TEMPLATE);\n\nexport default C4LModal;\n"],"names":["_interopRequireDefault","obj","__esModule","default","_modal","_modal_registry","C4LModal","Modal","static","registerEventListeners","super","ModalRegistry","register","TYPE","TEMPLATE","_exports"],"mappings":"kHAwBgD,SAAAA,uBAAAC,KAAAA,OAAAA,KAAAA,IAAAC,WAAAD,IAAAE,CAAAA,QAAAF,IAAA;;;;;;;qFADhDG,OAAAJ,uBAAAI,QACAC,gBAAAL,uBAAAK,iBAEA,MAAMC,SAAW,cAAcC,OAAAA,QAC3BC,YAAc,iBACdA,gBAAkB,iBAElBC,sBAAAA,GAEIC,MAAMD,wBACV,GAGJE,gBAAAA,QAAcC,SAASN,SAASO,KAAMP,SAAUA,SAASQ,UAAUC,SAAAZ,QAEpDG,SAAQ,OAAAS,SAAAZ,OAAA"} \ No newline at end of file +{"version":3,"file":"modal.min.js","sources":["../src/modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Elements Modal for Tiny.\n *\n * @module tiny_elements/modal\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport const ElementsModal = class extends Modal {\n static TYPE = 'tiny_elements/modal';\n static TEMPLATE = 'tiny_elements/modal';\n\n configure(modalConfig) {\n // Remove modal from DOM on close.\n modalConfig.removeOnClose = true;\n super.configure(modalConfig);\n }\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n }\n};\n"],"names":["ElementsModal","Modal","configure","modalConfig","removeOnClose","registerEventListeners"],"mappings":"sZAyBaA,sCAAgB,cAAcC,eAIvCC,UAAUC,aAENA,YAAYC,eAAgB,QACtBF,UAAUC,aAGpBE,+BAEUA,kCAXI,yDACI"} \ No newline at end of file diff --git a/amd/build/options.min.js b/amd/build/options.min.js index 855f34b..bda9e3e 100644 --- a/amd/build/options.min.js +++ b/amd/build/options.min.js @@ -1,11 +1,11 @@ -define("tiny_c4l/options",["exports","editor_tiny/options","./common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showPreview=_exports.register=_exports.isStudent=_exports.isC4LVisible=_exports.getpreviewCSS=_exports.getcustomComponents=_exports.getallowedComponents=void 0; +define("tiny_elements/options",["exports","editor_tiny/options","tiny_elements/common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showPreview=_exports.register=_exports.isStudent=_exports.isElementsVisible=_exports.getCssUrl=_exports.canManage=void 0; /** - * Options helper for C4L plugin. + * Options helper for Elements plugin. * - * @module tiny_c4l/options + * @module tiny_elements/options * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const isstudentName=(0,_options.getPluginOptionName)(_common.pluginName,"isstudent"),allowedcompsName=(0,_options.getPluginOptionName)(_common.pluginName,"allowedcomps"),showpreviewName=(0,_options.getPluginOptionName)(_common.pluginName,"showpreview"),viewc4lName=(0,_options.getPluginOptionName)(_common.pluginName,"viewc4l"),previewCSS=(0,_options.getPluginOptionName)(_common.pluginName,"previewcss"),customComps=(0,_options.getPluginOptionName)(_common.pluginName,"customcomps");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(allowedcompsName,{processor:"array",default:[]}),registerOption(isstudentName,{processor:"boolean",default:!1}),registerOption(showpreviewName,{processor:"boolean",default:!0}),registerOption(viewc4lName,{processor:"boolean",default:!0}),registerOption(previewCSS,{processor:"string",default:""}),registerOption(customComps,{processor:"array",default:[]})};_exports.isC4LVisible=editor=>editor.options.get(viewc4lName);_exports.isStudent=editor=>editor.options.get(isstudentName);_exports.showPreview=editor=>editor.options.get(showpreviewName);_exports.getallowedComponents=editor=>editor.options.get(allowedcompsName);_exports.getcustomComponents=editor=>editor.options.get(customComps);_exports.getpreviewCSS=editor=>editor.options.get(previewCSS)})); +const isstudentName=(0,_options.getPluginOptionName)(_common.pluginName,"isstudent"),showpreviewName=(0,_options.getPluginOptionName)(_common.pluginName,"showpreview"),viewElementsName=(0,_options.getPluginOptionName)(_common.pluginName,"viewelements"),cssUrlName=(0,_options.getPluginOptionName)(_common.pluginName,"cssurl"),canManageName=(0,_options.getPluginOptionName)(_common.pluginName,"canmanage");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(isstudentName,{processor:"boolean",default:!1}),registerOption(showpreviewName,{processor:"boolean",default:!0}),registerOption(viewElementsName,{processor:"boolean",default:!0}),registerOption(cssUrlName,{processor:"string",default:""}),registerOption(canManageName,{processor:"boolean",default:!1})};_exports.isElementsVisible=editor=>editor.options.get(viewElementsName);_exports.isStudent=editor=>editor.options.get(isstudentName);_exports.showPreview=editor=>editor.options.get(showpreviewName);_exports.getCssUrl=editor=>editor.options.get(cssUrlName);_exports.canManage=editor=>editor.options.get(canManageName)})); //# sourceMappingURL=options.min.js.map \ No newline at end of file diff --git a/amd/build/options.min.js.map b/amd/build/options.min.js.map index 1230cf2..df5f602 100644 --- a/amd/build/options.min.js.map +++ b/amd/build/options.min.js.map @@ -1 +1 @@ -{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Options helper for C4L plugin.\n *\n * @module tiny_c4l/options\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst isstudentName = getPluginOptionName(pluginName, 'isstudent');\nconst allowedcompsName = getPluginOptionName(pluginName, 'allowedcomps');\nconst showpreviewName = getPluginOptionName(pluginName, 'showpreview');\nconst viewc4lName = getPluginOptionName(pluginName, 'viewc4l');\nconst previewCSS = getPluginOptionName(pluginName, 'previewcss');\nconst customComps = getPluginOptionName(pluginName, 'customcomps');\n\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(allowedcompsName, {\n processor: 'array',\n \"default\": [],\n });\n\n registerOption(isstudentName, {\n processor: 'boolean',\n \"default\": false,\n });\n\n registerOption(showpreviewName, {\n processor: 'boolean',\n \"default\": true,\n });\n\n registerOption(viewc4lName, {\n processor: 'boolean',\n \"default\": true,\n });\n\n registerOption(previewCSS, {\n processor: 'string',\n \"default\": '',\n });\n\n registerOption(customComps, {\n processor: 'array',\n \"default\": [],\n });\n};\n\n/**\n * Get the permissions configuration for the Tiny C4L plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const isC4LVisible = (editor) => editor.options.get(viewc4lName);\n\n/**\n * Get whether user is a student configuration for the Tiny C4L plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const isStudent = (editor) => editor.options.get(isstudentName);\n\n/**\n * Get the preview visibility configuration for the Tiny C4L plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const showPreview = (editor) => editor.options.get(showpreviewName);\n\n/**\n * Get components allowed at students configuration for the Tiny C4L plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getallowedComponents = (editor) => editor.options.get(allowedcompsName);\n\n/**\n * Get custom components configuration for the Tiny C4L plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getcustomComponents = (editor) => editor.options.get(customComps);\n\n/**\n * Get custom preview CSS configuration for the Tiny C4L plugin.\n *\n * @param {TinyMCE} editor\n * @returns {string}\n */\nexport const getpreviewCSS = (editor) => editor.options.get(previewCSS);\n"],"names":["isstudentName","getPluginOptionName","pluginName","allowedcompsName","showpreviewName","viewc4lName","previewCSS","customComps","_exports","register","editor","registerOption","options","processor","default","isC4LVisible","get","isStudent","showPreview","getallowedComponents","getcustomComponents","getpreviewCSS"],"mappings":";;;;;;;;AA0BA,MAAMA,eAAgB,EAAAC,SAAAA,qBAAoBC,QAAUA,WAAE,aAChDC,kBAAmB,EAAAF,SAAAA,qBAAoBC,QAAUA,WAAE,gBACnDE,iBAAkB,EAAAH,SAAAA,qBAAoBC,QAAUA,WAAE,eAClDG,aAAc,EAAAJ,SAAAA,qBAAoBC,QAAUA,WAAE,WAC9CI,YAAa,EAAAL,SAAAA,qBAAoBC,QAAUA,WAAE,cAC7CK,aAAc,EAAAN,SAAAA,qBAAoBC,QAAUA,WAAE,eAkClDM,SAAAC,SAhCuBC,SACrB,MAAMC,eAAiBD,OAAOE,QAAQH,SAEtCE,eAAeR,iBAAkB,CAC7BU,UAAW,QACXC,QAAW,KAGfH,eAAeX,cAAe,CAC1Ba,UAAW,UACXC,SAAY,IAGhBH,eAAeP,gBAAiB,CAC5BS,UAAW,UACXC,SAAY,IAGhBH,eAAeN,YAAa,CACxBQ,UAAW,UACXC,SAAY,IAGhBH,eAAeL,WAAY,CACvBO,UAAW,SACXC,QAAW,KAGfH,eAAeJ,YAAa,CACxBM,UAAW,QACXC,QAAW,IACb,EASkEN,SAAAO,aAA3CL,QAAWA,OAAOE,QAAQI,IAAIX,aAQYG,SAAAS,UAA7CP,QAAWA,OAAOE,QAAQI,IAAIhB,eAQmBQ,SAAAU,YAA/CR,QAAWA,OAAOE,QAAQI,IAAIZ,iBAQ2BI,SAAAW,qBAAhDT,QAAWA,OAAOE,QAAQI,IAAIb,kBAQYK,SAAAY,oBAA3CV,QAAWA,OAAOE,QAAQI,IAAIT,aAQMC,SAAAa,cAA1CX,QAAWA,OAAOE,QAAQI,IAAIV,WAAY"} \ No newline at end of file +{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Options helper for Elements plugin.\n *\n * @module tiny_elements/options\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from 'tiny_elements/common';\n\nconst isstudentName = getPluginOptionName(pluginName, 'isstudent');\nconst showpreviewName = getPluginOptionName(pluginName, 'showpreview');\nconst viewElementsName = getPluginOptionName(pluginName, 'viewelements');\nconst cssUrlName = getPluginOptionName(pluginName, 'cssurl');\nconst canManageName = getPluginOptionName(pluginName, 'canmanage');\n\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(isstudentName, {\n processor: 'boolean',\n \"default\": false,\n });\n\n registerOption(showpreviewName, {\n processor: 'boolean',\n \"default\": true,\n });\n\n registerOption(viewElementsName, {\n processor: 'boolean',\n \"default\": true,\n });\n\n registerOption(cssUrlName, {\n processor: 'string',\n \"default\": '',\n });\n\n registerOption(canManageName, {\n processor: 'boolean',\n \"default\": false,\n });\n};\n\n/**\n * Get the permissions configuration for the Tiny Elements plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const isElementsVisible = (editor) => editor.options.get(viewElementsName);\n\n/**\n * Get whether user is a student configuration for the Tiny Elements plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const isStudent = (editor) => editor.options.get(isstudentName);\n\n/**\n * Get the preview visibility configuration for the Tiny Elements plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const showPreview = (editor) => editor.options.get(showpreviewName);\n\n/**\n * Get the css url for the Tiny Elements plugin (to be used in the editor).\n * @param {TinyMCE} editor\n * @returns {string}\n */\nexport const getCssUrl = (editor) => editor.options.get(cssUrlName);\n\n/**\n * Whether the use hat tiny_elements/manage capability.\n * @param {TinyMCE} editor\n * @returns boolean\n */\nexport const canManage = (editor) => editor.options.get(canManageName);\n"],"names":["isstudentName","pluginName","showpreviewName","viewElementsName","cssUrlName","canManageName","editor","registerOption","options","register","processor","get"],"mappings":";;;;;;;;MA0BMA,eAAgB,gCAAoBC,mBAAY,aAChDC,iBAAkB,gCAAoBD,mBAAY,eAClDE,kBAAmB,gCAAoBF,mBAAY,gBACnDG,YAAa,gCAAoBH,mBAAY,UAC7CI,eAAgB,gCAAoBJ,mBAAY,+BAE7BK,eACfC,eAAiBD,OAAOE,QAAQC,SAEtCF,eAAeP,cAAe,CAC1BU,UAAW,mBACC,IAGhBH,eAAeL,gBAAiB,CAC5BQ,UAAW,mBACC,IAGhBH,eAAeJ,iBAAkB,CAC7BO,UAAW,mBACC,IAGhBH,eAAeH,WAAY,CACvBM,UAAW,iBACC,KAGhBH,eAAeF,cAAe,CAC1BK,UAAW,mBACC,gCAUcJ,QAAWA,OAAOE,QAAQG,IAAIR,qCAQtCG,QAAWA,OAAOE,QAAQG,IAAIX,oCAQ5BM,QAAWA,OAAOE,QAAQG,IAAIT,oCAOhCI,QAAWA,OAAOE,QAAQG,IAAIP,+BAO9BE,QAAWA,OAAOE,QAAQG,IAAIN"} \ No newline at end of file diff --git a/amd/build/plugin.min.js b/amd/build/plugin.min.js index 290c7e4..57200b1 100644 --- a/amd/build/plugin.min.js +++ b/amd/build/plugin.min.js @@ -1,10 +1,10 @@ -define("tiny_c4l/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./options","./commands","./configuration"],(function(_exports,_loader,_utils,_common,_options,_commands,Configuration){function _getRequireWildcardCache(e){if("function"!=typeof WeakMap)return null;var r=new WeakMap,t=new WeakMap;return(_getRequireWildcardCache=function(e){return e?t:r})(e)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Configuration=function(e,r){if(!r&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var t=_getRequireWildcardCache(r);if(t&&t.has(e))return t.get(e);var n={__proto__:null},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var u in e)if("default"!==u&&{}.hasOwnProperty.call(e,u)){var i=a?Object.getOwnPropertyDescriptor(e,u):null;i&&(i.get||i.set)?Object.defineProperty(n,u,i):n[u]=e[u]}return n.default=e,t&&t.set(e,n),n +define("tiny_elements/plugin",["exports","editor_tiny/loader","editor_tiny/utils","tiny_elements/common","tiny_elements/options","tiny_elements/commands","tiny_elements/configuration"],(function(_exports,_loader,_utils,_common,_options,_commands,Configuration){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Configuration=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj} /** - * Tiny C4L plugin. + * Tiny Elements plugin. * - * @module tiny_c4l/plugin + * @module tiny_elements/plugin * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */}(Configuration);_exports.default=new Promise((async resolve=>{const[tinyMCE,pluginMetadata,setupCommands]=await Promise.all([(0,_loader.getTinyMCE)(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName),(0,_commands.getSetup)()]);tinyMCE.PluginManager.add(_common.pluginName,(editor=>((0,_options.register)(editor),setupCommands(editor),pluginMetadata))),resolve([_common.pluginName,Configuration])}));return _exports.default})); + */(Configuration);var _default=new Promise((async resolve=>{const[tinyMCE,pluginMetadata,setupCommands]=await Promise.all([(0,_loader.getTinyMCE)(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName),(0,_commands.getSetup)()]);tinyMCE.PluginManager.add(_common.pluginName,(editor=>((0,_options.register)(editor),setupCommands(editor),pluginMetadata))),resolve([_common.pluginName,Configuration])}));return _exports.default=_default,_exports.default})); //# sourceMappingURL=plugin.min.js.map \ No newline at end of file diff --git a/amd/build/plugin.min.js.map b/amd/build/plugin.min.js.map index b087ba4..83ac1c1 100644 --- a/amd/build/plugin.min.js.map +++ b/amd/build/plugin.min.js.map @@ -1 +1 @@ -{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L plugin.\n *\n * @module tiny_c4l/plugin\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport {register as registerOptions} from './options';\nimport {getSetup as getCommandSetup} from './commands';\nimport * as Configuration from './configuration';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n pluginMetadata,\n setupCommands,\n ] = await Promise.all([\n getTinyMCE(),\n getPluginMetadata(component, pluginName),\n getCommandSetup(),\n ]);\n\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n // Register options.\n registerOptions(editor);\n\n // Setup commands.\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n // Resolve the C4L Plugin and include configuration.\n resolve([pluginName, Configuration]);\n});\n"],"names":["_getRequireWildcardCache","e","WeakMap","r","t","Configuration","__esModule","default","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","_interopRequireWildcard","_exports","Promise","async","tinyMCE","pluginMetadata","setupCommands","all","getTinyMCE","getPluginMetadata","component","pluginName","getCommandSetup","PluginManager","add","editor","registerOptions","resolve"],"mappings":"gNA6BiD,SAAAA,yBAAAC,GAAA,GAAA,mBAAAC,QAAA,OAAA,KAAA,IAAAC,EAAAD,IAAAA,QAAAE,EAAAF,IAAAA,eAAAF,yBAAA,SAAAC,GAAAA,OAAAA,EAAAG,EAAAD,IAAAF,EAAA,iFAAjDI,cAAiD,SAAAJ,EAAAE,GAAAA,IAAAA,GAAAF,GAAAA,EAAAK,WAAAL,OAAAA,EAAAA,GAAAA,OAAAA,GAAAA,iBAAAA,GAAAA,mBAAAA,EAAAM,MAAAA,CAAAA,QAAAN,GAAAG,IAAAA,EAAAJ,yBAAAG,GAAA,GAAAC,GAAAA,EAAAI,IAAAP,GAAA,OAAAG,EAAAK,IAAAR,GAAA,IAAAS,EAAA,CAAAC,UAAA,MAAAC,EAAAC,OAAAC,gBAAAD,OAAAE,yBAAA,IAAA,IAAAC,KAAAf,EAAAe,GAAAA,YAAAA,GAAAC,CAAAA,EAAAA,eAAAC,KAAAjB,EAAAe,GAAAG,CAAAA,IAAAA,EAAAP,EAAAC,OAAAE,yBAAAd,EAAAe,GAAAG,KAAAA,IAAAA,EAAAV,KAAAU,EAAAC,KAAAP,OAAAC,eAAAJ,EAAAM,EAAAG,GAAAT,EAAAM,GAAAf,EAAAe,GAAAN,OAAAA,EAAAH,QAAAN,EAAAG,GAAAA,EAAAgB,IAAAnB,EAAAS,GAAAA;;;;;;;KAAA,CAAjDW,CAAAhB,eAdAiB,SAAAf,QAiBe,IAAIgB,SAAQC,gBACvB,MACIC,QACAC,eACAC,qBACMJ,QAAQK,IAAI,EAClB,EAAAC,QAAAA,eACA,EAAAC,OAAiBA,mBAACC,kBAAWC,QAAAA,aAC7B,EAAAC,UAAAA,cAGJR,QAAQS,cAAcC,IAAIH,QAAUA,YAAGI,UAEnC,EAAAC,SAAAA,UAAgBD,QAGhBT,cAAcS,QAEPV,kBAIXY,QAAQ,CAACN,QAAAA,WAAY3B,eAAe,IACtC,OAAAiB,SAAAf,OAAA"} \ No newline at end of file +{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements plugin.\n *\n * @module tiny_elements/plugin\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from 'tiny_elements/common';\nimport {register as registerOptions} from 'tiny_elements/options';\nimport {getSetup as getCommandSetup} from 'tiny_elements/commands';\nimport * as Configuration from 'tiny_elements/configuration';\n\n// Setup the tiny_elements Plugin.\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n pluginMetadata,\n setupCommands,\n ] = await Promise.all([\n getTinyMCE(),\n getPluginMetadata(component, pluginName),\n getCommandSetup(),\n ]);\n\n tinyMCE.PluginManager.add(pluginName, (editor) => {\n // Register options.\n registerOptions(editor);\n\n // Setup commands.\n setupCommands(editor);\n\n return pluginMetadata;\n });\n\n // Resolve the Elements Plugin and include configuration.\n resolve([pluginName, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","pluginMetadata","setupCommands","all","component","pluginName","PluginManager","add","editor","resolve","Configuration"],"mappings":";;;;;;;kCAiCe,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,eACAC,qBACMJ,QAAQK,IAAI,EAClB,yBACA,4BAAkBC,kBAAWC,qBAC7B,0BAGJL,QAAQM,cAAcC,IAAIF,oBAAaG,+BAEnBA,QAGhBN,cAAcM,QAEPP,kBAIXQ,QAAQ,CAACJ,mBAAYK"} \ No newline at end of file diff --git a/amd/build/preferencelib.min.js b/amd/build/preferencelib.min.js new file mode 100644 index 0000000..39ba544 --- /dev/null +++ b/amd/build/preferencelib.min.js @@ -0,0 +1,11 @@ +define("tiny_elements/preferencelib",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Helper for handling user preferences. + * + * @module tiny_elements/preferencelib + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.savePreferences=_exports.loadPreferences=_exports.Preferences=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.Preferences={category:"elements_category",category_flavors:"elements_category_flavors",component_variants:"elements_component_variants"};_exports.loadPreferences=async name=>{const request={methodname:"core_user_get_user_preferences",args:{name:name}};await _ajax.default.call([request])[0].then((result=>{try{return JSON.parse(result.preferences[0].value)}catch(err){return _notification.default.exception(err),{}}})).catch((err=>(_notification.default.exception(err),{})))};_exports.savePreferences=rawPreferences=>{const request={methodname:"core_user_update_user_preferences",args:{preferences:rawPreferences}};return _ajax.default.call([request])[0].catch(_notification.default.exception)}})); + +//# sourceMappingURL=preferencelib.min.js.map \ No newline at end of file diff --git a/amd/build/preferencelib.min.js.map b/amd/build/preferencelib.min.js.map new file mode 100644 index 0000000..5843ea9 --- /dev/null +++ b/amd/build/preferencelib.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"preferencelib.min.js","sources":["../src/preferencelib.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper for handling user preferences.\n *\n * @module tiny_elements/preferencelib\n * @copyright 2024 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport const Preferences = {\n category: 'elements_category',\n // eslint-disable-next-line camelcase\n category_flavors: 'elements_category_flavors',\n // eslint-disable-next-line camelcase\n component_variants: 'elements_component_variants'\n};\n\n/**\n * Load user preferences.\n * @param {string} name\n * @returns {object}\n */\nexport const loadPreferences = async(name) => {\n const request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n name: name\n }\n };\n\n await Ajax.call([request])[0]\n .then(result => {\n try {\n let preferences = JSON.parse(result.preferences[0].value);\n return preferences;\n } catch (err) {\n Notification.exception(err);\n return {};\n }\n }).catch(err => {\n Notification.exception(err);\n return {};\n });\n};\n\n/**\n * Save user preferences.\n * @param {object} rawPreferences\n * @returns {Promise}\n */\nexport const savePreferences = (rawPreferences) => {\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: rawPreferences\n }\n };\n\n return Ajax.call([request])[0].catch(Notification.exception);\n};\n"],"names":["category","category_flavors","component_variants","async","request","methodname","args","name","Ajax","call","then","result","JSON","parse","preferences","value","err","exception","catch","rawPreferences","Notification"],"mappings":";;;;;;;;wPA2B2B,CACvBA,SAAU,oBAEVC,iBAAkB,4BAElBC,mBAAoB,wDAQOC,MAAAA,aACrBC,QAAU,CACZC,WAAY,iCACZC,KAAM,CACFC,KAAMA,aAIRC,cAAKC,KAAK,CAACL,UAAU,GACtBM,MAAKC,oBAEoBC,KAAKC,MAAMF,OAAOG,YAAY,GAAGC,OAErD,MAAOC,kCACQC,UAAUD,KAChB,OAEZE,OAAMF,4BACQC,UAAUD,KAChB,gCASaG,uBACtBf,QAAU,CACZC,WAAY,oCACZC,KAAM,CACFQ,YAAaK,wBAIdX,cAAKC,KAAK,CAACL,UAAU,GAAGc,MAAME,sBAAaH"} \ No newline at end of file diff --git a/amd/build/previewmodal.min.js b/amd/build/previewmodal.min.js new file mode 100644 index 0000000..5656a95 --- /dev/null +++ b/amd/build/previewmodal.min.js @@ -0,0 +1,3 @@ +define("tiny_elements/previewmodal",["exports","core/modal"],(function(_exports,_modal){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.PreviewModal=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class PreviewModal extends _modal.default{configure(modalConfig){modalConfig.removeOnClose=!0,modalConfig.large=!0,super.configure(modalConfig)}}_exports.PreviewModal=PreviewModal,_defineProperty(PreviewModal,"TYPE","tiny_elements/management_preview"),_defineProperty(PreviewModal,"TEMPLATE","tiny_elements/management_preview")})); + +//# sourceMappingURL=previewmodal.min.js.map \ No newline at end of file diff --git a/amd/build/previewmodal.min.js.map b/amd/build/previewmodal.min.js.map new file mode 100644 index 0000000..9dc2ddf --- /dev/null +++ b/amd/build/previewmodal.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"previewmodal.min.js","sources":["../src/previewmodal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Component preview modal.\n *\n * @module tiny_elements/previewmodal\n * @copyright 2025 ISB Bayern\n * @author Stefan Hanauska \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\n\nexport class PreviewModal extends Modal {\n static TYPE = \"tiny_elements/management_preview\";\n static TEMPLATE = \"tiny_elements/management_preview\";\n configure(modalConfig) {\n modalConfig.removeOnClose = true;\n modalConfig.large = true;\n super.configure(modalConfig);\n }\n}\n"],"names":["PreviewModal","Modal","configure","modalConfig","removeOnClose","large"],"mappings":"qZA0BaA,qBAAqBC,eAG9BC,UAAUC,aACNA,YAAYC,eAAgB,EAC5BD,YAAYE,OAAQ,QACdH,UAAUC,iEANXH,oBACK,oDADLA,wBAES"} \ No newline at end of file diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js index 58762bd..dfaa072 100644 --- a/amd/build/ui.min.js +++ b/amd/build/ui.min.js @@ -1,10 +1,10 @@ -define("tiny_c4l/ui",["exports","./common","./modal","core/modal_factory","./components","core/str","./options","core/modal_events","./variantslib"],(function(_exports,_common,_modal,_modal_factory,_components,_str,_options,_modal_events,_variantslib){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("tiny_elements/ui",["exports","tiny_elements/modal","tiny_elements/options","core/modal_events","tiny_elements/variantslib","tiny_elements/preferencelib","editor_tiny/options","tiny_elements/data"],(function(_exports,_modal,_options,_modal_events,_variantslib,_preferencelib,_options2,_data){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** - * Tiny C4L UI. + * Tiny Elements UI. * - * @module tiny_c4l/ui + * @module tiny_elements/ui * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal=_interopRequireDefault(_modal),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events);let userStudent=!1,previewC4L=!0,allowedComponents=[],Contexts=[],langStrings={},previewCSS="",customComponents=[];_exports.handleAction=async editor=>{userStudent=(0,_options.isStudent)(editor),previewC4L=(0,_options.showPreview)(editor),customComponents=(0,_options.getcustomComponents)(editor),addCustomComponents(),allowedComponents=(0,_options.getallowedComponents)(editor),previewCSS=(0,_options.getpreviewCSS)(editor),langStrings=await getAllStrings(),(0,_variantslib.loadVariantPreferences)(_components.components).then((()=>displayDialogue(editor)))};const displayDialogue=async editor=>{const data=Object.assign({},{}),modal=await _modal_factory.default.create({type:_modal.default.TYPE,templateContext:await getTemplateContext(editor,data),large:!0}),modalClass=previewC4L?"c4l-modal":"c4l-modal-no-preview";if(editor.targetElm.closest("body").classList.add(modalClass),""!==previewCSS){const styles=document.createElement("style");styles.textContent=previewCSS,editor.targetElm.closest("body").appendChild(styles)}modal.show(),modal.getRoot().on(_modal_events.default.hidden,(()=>{handleModalHidden(editor)}));modal.getRoot()[0].querySelectorAll(".c4l-button-filter").forEach((node=>{node.addEventListener("click",(event=>{handleButtonFilterClick(event,modal)}))})),modal.getRoot()[0].querySelector(".c4l-select-filter").addEventListener("change",(event=>{handleSelectFilterChange(event,modal)}));modal.getRoot()[0].querySelectorAll(".c4lt-dialog-button").forEach((node=>{node.addEventListener("click",(event=>{handleButtonClick(event,editor,modal)})),previewC4L&&(node.addEventListener("mouseenter",(event=>{handleButtonMouseEvent(event,modal,!0)})),node.addEventListener("mouseleave",(event=>{handleButtonMouseEvent(event,modal,!1)})))}));modal.getRoot()[0].querySelectorAll(".c4l-button-variant").forEach((node=>{node.addEventListener("click",(event=>{handleVariantClick(event,modal)})),previewC4L&&(node.addEventListener("mouseenter",(event=>{handleVariantMouseEvent(event,modal,!0)})),node.addEventListener("mouseleave",(event=>{handleVariantMouseEvent(event,modal,!1)})))}))},handleSelectFilterChange=(event,modal)=>{const select=event.target.closest("select");if(select){const currentContext=select.value;if(-1!==Contexts.indexOf(currentContext)){modal.getRoot()[0].querySelectorAll(".c4l-buttons-filters button").forEach((node=>node.classList.remove("c4l-button-filter-enabled")));modal.getRoot()[0].querySelector('.c4l-button-filter[data-filter="'+currentContext+'"]').classList.add("c4l-button-filter-enabled"),showContextButtons(modal,currentContext)}}},handleButtonFilterClick=(event,modal)=>{const button=event.target.closest("button"),currentContext=button.dataset.filter;if(-1!==Contexts.indexOf(currentContext)){modal.getRoot()[0].querySelectorAll(".c4l-buttons-filters button").forEach((node=>node.classList.remove("c4l-button-filter-enabled"))),button.classList.add("c4l-button-filter-enabled");modal.getRoot()[0].querySelector(".c4l-select-filter").selectedIndex=Contexts.indexOf(currentContext),showContextButtons(modal,currentContext)}},handleModalHidden=editor=>{editor.targetElm.closest("body").classList.remove("c4l-modal-no-preview"),(0,_variantslib.saveVariantPreferences)(_components.components)},handleButtonClick=(event,editor,modal)=>{const selectedButton=event.target.closest("button").dataset.id,component=_components.components.find((element=>element.name==selectedButton));if(null!=component){const sel=editor.selection.getContent();let componentCode=component.code;const placeholder=sel.length>0?sel:component.text,randomId=generateRandomID(),newNode=document.createElement("span");newNode.dataset.id=randomId,newNode.innerHTML=placeholder,componentCode=componentCode.replace("{{PLACEHOLDER}}",newNode.outerHTML);const variants=(0,_variantslib.getVariantsClass)(component.name);variants.length>0?(componentCode=componentCode.replace("{{VARIANTS}}",variants.join(" ")),componentCode=componentCode.replace("{{VARIANTSHTML}}",(0,_variantslib.getVariantsHtml)(component.name))):(componentCode=componentCode.replace("{{VARIANTS}}",""),componentCode=componentCode.replace("{{VARIANTSHTML}}","")),componentCode=applyRandomID(componentCode),componentCode=applyLangStrings(componentCode),editor.selection.setContent(componentCode);const nodeSel=editor.dom.select('span[data-id="'+randomId+'"]');nodeSel?.[0]&&editor.selection.select(nodeSel[0]),modal.destroy(),editor.focus()}},handleButtonMouseEvent=(event,modal,show)=>{const selectedButton=event.target.closest("button").dataset.id,node=modal.getRoot()[0].querySelector('div[data-id="code-preview-'+selectedButton+'"]'),previewDefault=modal.getRoot()[0].querySelector('div[data-id="code-preview-default"]');node&&(show?(previewDefault.classList.toggle("c4l-hidden"),node.classList.toggle("c4l-hidden")):(node.classList.toggle("c4l-hidden"),previewDefault.classList.toggle("c4l-hidden")))},handleVariantMouseEvent=(event,modal,show)=>{const variant=event.target.closest("span"),variantEnabled="on"==variant.dataset.state,button=event.target.closest("button");variantEnabled||updateVariantComponentState(variant,button,modal,show,!1)},handleVariantClick=(event,modal)=>{event.stopPropagation();const variant=event.target.closest("span"),button=event.target.closest("button");updateVariantComponentState(variant,button,modal,!1,!0)},getTemplateContext=async(editor,data)=>Object.assign({},{elementid:editor.id,buttons:await getButtons(editor),filters:await getFilters(),preview:previewC4L},data),getFilters=async()=>{const filters=[],stringValues=await(0,_str.get_strings)(Contexts.map((key=>({key:key,component:_common.component}))));return Contexts.forEach(((context,index)=>{filters.push({name:stringValues[index],type:context,filterClass:0===index?"c4l-button-filter-enabled":""})})),filters},getButtons=editor=>{const buttons=[],sel=editor.selection.getContent();let componentCode="",placeholder="",variants=[],buttonText="";return _components.components.forEach((component=>{if(!userStudent||userStudent&&allowedComponents.includes(component.name)){if(previewC4L){placeholder=sel.length>0?sel:component.text,componentCode=component.code,componentCode=componentCode.replace("{{PLACEHOLDER}}",placeholder),variants=(0,_variantslib.getVariantsClass)(component.name);const variantsNode=document.createElement("span");variantsNode.dataset.id="variantHTML-"+component.id,variants.length>0?(componentCode=componentCode.replace("{{VARIANTS}}",variants.join(" ")),variantsNode.innerHTML=(0,_variantslib.getVariantsHtml)(component.name),componentCode=componentCode.replace("{{VARIANTSHTML}}",variantsNode.outerHTML)):(componentCode=componentCode.replace("{{VARIANTS}}",""),componentCode=componentCode.replace("{{VARIANTSHTML}}",variantsNode.outerHTML)),componentCode=applyLangStrings(componentCode)}-1===Contexts.indexOf(component.type)&&Contexts.push(component.type),buttonText="custom"==component.type?component.buttonname:langStrings.get(component.name),buttons.push({id:component.name,name:buttonText,type:component.type,icon:component.icon??"",imageClass:component.imageClass,classComponent:"c4lv-"+component.name,htmlcode:componentCode,css:component.css??"",variants:getVariantsState(component.name,component.variants)}),0!==Contexts.indexOf(component.type)&&(buttons[buttons.length-1].imageClass+=" c4l-hidden")}})),buttons},getVariantsState=(component,elements)=>{const variants=[];let variantState="",variantClass="";return elements.length>3&&(elements=elements.slice(0,2)),elements.forEach(((variant,index)=>{(0,_variantslib.variantExists)(component,variant)?(variantState="on",variantClass="on "):(variantState="off",variantClass=""),variantClass+=variant+"-variant-"+variantState,variants.push({id:index,name:variant,state:variantState,imageClass:variantClass,title:langStrings.get(variant)})})),variants},updateVariantComponentState=(variant,button,modal,show,updateHtml)=>{const selectedVariant="c4l-"+variant.dataset.variant+"-variant",component=_components.components.find((element=>element.name==button.dataset.id)),componentClass=button.dataset.classcomponent,previewComponent=modal.getRoot()[0].querySelector('div[data-id="code-preview-'+button.dataset.id+'"] .'+componentClass),variantPreview=modal.getRoot()[0].querySelector('span[data-id="variantHTML-'+button.dataset.id+'"]');let variantsHtml="";previewComponent?updateHtml?("on"==variant.dataset.state?((0,_variantslib.removeVariant)(component.name,variant.dataset.variant),updateVariantButtonState(variant,!1),previewComponent.classList.remove(selectedVariant)):((0,_variantslib.addVariant)(component.name,variant.dataset.variant),updateVariantButtonState(variant,!0),previewComponent.classList.add(selectedVariant)),variantPreview&&(variantPreview.innerHTML=(0,_variantslib.getVariantsHtml)(component.name))):(variantsHtml=(0,_variantslib.getVariantsHtml)(component.name),show?(previewComponent.classList.add(selectedVariant),variantsHtml+=(0,_variantslib.getVariantHtml)(variant.dataset.variant)):previewComponent.classList.remove(selectedVariant),variantPreview&&(variantPreview.innerHTML=variantsHtml)):updateHtml&&("on"==variant.dataset.state?((0,_variantslib.removeVariant)(component.name,variant.dataset.variant),updateVariantButtonState(variant,!1)):((0,_variantslib.addVariant)(component.name,variant.dataset.variant),updateVariantButtonState(variant,!0)))},updateVariantButtonState=(variant,activate)=>{activate?(variant.dataset.state="on",variant.classList.remove(variant.dataset.variant+"-variant-off"),variant.classList.add(variant.dataset.variant+"-variant-on"),variant.classList.add("on")):(variant.dataset.state="off",variant.classList.remove(variant.dataset.variant+"-variant-on"),variant.classList.add(variant.dataset.variant+"-variant-off"),variant.classList.remove("on"))},showContextButtons=(modal,context)=>{const showNodes=modal.getRoot()[0].querySelectorAll('button[data-type="'+context+'"]'),hideNodes=modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type="'+context+'"])');showNodes.forEach((node=>node.classList.remove("c4l-hidden"))),hideNodes.forEach((node=>node.classList.add("c4l-hidden")))},applyLangStrings=text=>([...text.matchAll(/{{#([^}]*)}}/g)].forEach((strLang=>{text=text.replace("{{#"+strLang[1]+"}}",langStrings.get(strLang[1]))})),text),generateRandomID=()=>{const timestamp=(new Date).getTime();return"R"+Math.round(1e5*Math.random())+"-"+timestamp},applyRandomID=text=>{const compRegex=/{{@ID}}/g;return text.match(compRegex)&&(text=text.replace(compRegex,generateRandomID())),text},getAllStrings=async()=>{const keys=[],compRegex=/{{#([^}]*)}}/g;_components.components.forEach((element=>{-1==element.name.indexOf("customcomp")&&keys.push(element.name),element.variants.forEach((variant=>{-1===keys.indexOf(variant)&&keys.push(variant)})),[...element.code.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])})),[...element.text.matchAll(compRegex)].forEach((strLang=>{-1===keys.indexOf(strLang[1])&&keys.push(strLang[1])}))}));const stringValues=await(0,_str.get_strings)(keys.map((key=>({key:key,component:_common.component}))));return new Map(keys.map(((key,index)=>[key,stringValues[index]])))},addCustomComponents=()=>{customComponents.length>0&&customComponents.forEach((customcomp=>{null==_components.components.find((element=>element.id==customcomp.id+1e3))&&_components.components.push({id:customcomp.id+1e3,name:customcomp.name,buttonname:customcomp.buttonname,type:"custom",imageClass:"c4l-custom-icon",code:replaceCustomPlaceholders(customcomp),text:customcomp.text.length>0?customcomp.text:"{{#textplaceholder}}",variants:customcomp.variants?["full-width"]:[],icon:customcomp.icon,css:customcomp.css})}))},replaceCustomPlaceholders=component=>{let html=component.code;const variants=component.variants?" {{VARIANTS}}":"";return html=html.replace("{{CUSTOMCLASS}}","c4lv-"+component.name+" c4lv-custom-component"+variants),html}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.handleAction=void 0,_modal_events=_interopRequireDefault(_modal_events),_data=_interopRequireDefault(_data);let currentFlavor="",currentFlavorId=0,currentCategoryId=1,currentCategoryName="",lastFlavor=[],selection="",data={};_exports.handleAction=async editor=>{selection=editor.selection.getContent(),data=new _data.default((0,_options2.getContextId)(editor),(0,_options.isStudent)(editor),(0,_options.showPreview)(editor),(0,_options.canManage)(editor)),await data.loadData(),(0,_variantslib.setData)(data),currentCategoryId=(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.category),lastFlavor=(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.category_flavors),null===lastFlavor&&(lastFlavor=[]);let componentVariants=(0,_preferencelib.loadPreferences)(_preferencelib.Preferences.component_variants);null===componentVariants&&(componentVariants={}),(0,_variantslib.loadVariantPreferences)(componentVariants),await displayDialogue(editor)};const displayDialogue=async editor=>{const templateContext=data.getTemplateContext(editor),modal=await _modal.ElementsModal.create({type:_modal.ElementsModal.TYPE,templateContext:templateContext,large:!0}),modalClass=data.getPreviewElements()?"elements-modal":"elements-modal-no-preview";editor.targetElm.closest("body").classList.add(modalClass),modal.show(),modal.getRoot().on(_modal_events.default.hidden,(()=>{handleModalHidden(editor)}));const soleCategories=modal.getRoot()[0].querySelectorAll(".elements-category.no-flavors");soleCategories.forEach((node=>{node.addEventListener("click",(event=>{handleCategoryClick(event,modal)}))}));const selectCategories=modal.getRoot()[0].querySelectorAll(".elements-category-flavor");selectCategories.forEach((node=>{node.addEventListener("click",(event=>{handleCategoryFlavorClick(event,modal)}))}));modal.getRoot()[0].querySelectorAll(".nav-link.dropdown-toggle").forEach((node=>{node.addEventListener("click",(event=>{handleCategoryRemember(event,modal)}))}));modal.getRoot()[0].querySelectorAll(".elementst-dialog-button").forEach((node=>{node.addEventListener("click",(event=>{handleButtonClick(event,editor,modal)})),data.getPreviewElements()&&(node.addEventListener("mouseenter",(event=>{handleButtonMouseEvent(event,modal,!0)})),node.addEventListener("mouseleave",(event=>{handleButtonMouseEvent(event,modal,!1)})))}));if(modal.getRoot()[0].querySelectorAll(".elements-button-variant").forEach((node=>{node.addEventListener("click",(event=>{handleVariantClick(event,modal)}))})),soleCategories.length>0||selectCategories.length>0){const savedCategory=currentCategoryId,savedFlavor=lastFlavor[currentCategoryId];if(0==soleCategories.length||soleCategories[0].displayorder>selectCategories[0].displayorder?selectCategories[0].click():soleCategories[0].click(),0!=savedCategory&&(soleCategories.forEach((node=>{node.dataset.categoryid==savedCategory&&node.click()})),selectCategories.forEach((node=>{node.dataset.categoryid==savedCategory&&node.click()})),savedFlavor)){const flavorlink=modal.getRoot()[0].querySelector('.elements-category-flavor[data-id="'+savedFlavor+'"]');flavorlink&&flavorlink.click()}}},handleCategoryClick=(event,modal)=>{const link=event.target;currentCategoryId=link.dataset.categoryid,currentCategoryName=link.dataset.categoryname;modal.getRoot()[0].querySelectorAll(".nav-link, .dropdown-item").forEach((node=>node.classList.remove("active"))),link.classList.add("active"),showCategoryButtons(modal,currentCategoryName)},handleCategoryFlavorClick=(event,modal)=>{const link=event.target;currentFlavor=link.dataset.flavor,currentFlavorId=link.dataset.id,currentCategoryId=link.dataset.categoryid,currentCategoryName=link.dataset.categoryname,lastFlavor[currentCategoryId]=currentFlavorId;modal.getRoot()[0].querySelectorAll(".nav-link, .dropdown-item").forEach((node=>node.classList.remove("active"))),link.classList.add("active");modal.getRoot()[0].querySelector('.nav-link[data-categoryid="'+currentCategoryId+'"]').classList.add("active");modal.getRoot()[0].querySelectorAll(".elements-buttons-preview button").forEach((componentButton=>{if(null!=componentButton.dataset.flavor&&componentButton.classList.remove(componentButton.dataset.flavor),componentButton.classList.add(currentFlavor),componentButton.dataset.flavor=currentFlavor,""!=componentButton.dataset.flavorlist&&!componentButton.dataset.flavorlist.split(",").includes(currentFlavor)||componentButton.dataset.category!=currentCategoryName)componentButton.classList.add("elements-hidden");else if(componentButton.classList.remove("elements-hidden"),""!=componentButton.dataset.flavorlist){let variants=(0,_variantslib.getVariantsClass)(data.getComponentById(componentButton.dataset.id).name,currentFlavor);componentButton.querySelectorAll(".elements-button-variant").forEach((variant=>{updateVariantButtonState(variant,-1!=variants.indexOf(variant.dataset.variantclass))}))}}))},handleCategoryRemember=(event,modal)=>{const link=event.target;if(currentCategoryId=link.dataset.categoryid,currentCategoryName=link.dataset.categoryname,currentFlavorId=lastFlavor[currentCategoryId],null!=currentFlavorId){let e={target:modal.getRoot()[0].querySelector('.elements-category-flavor[data-id="'+currentFlavorId+'"]')};handleCategoryFlavorClick(e,modal)}},handleModalHidden=editor=>{editor.targetElm.closest("body").classList.remove("elements-modal-no-preview"),0!=currentCategoryId&&0!=currentFlavorId&&(0,_preferencelib.savePreferences)([{type:_preferencelib.Preferences.category,value:currentCategoryId},{type:_preferencelib.Preferences.category_flavors,value:JSON.stringify(lastFlavor)},{type:_preferencelib.Preferences.component_variants,value:JSON.stringify((0,_variantslib.getVariantPreferences)())}])},updateComponentCode=function(componentCode,selectedButton,placeholder){let flavor=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"";componentCode=componentCode.replace("{{PLACEHOLDER}}",placeholder);const comp=data.getComponentById(selectedButton),variants=(0,_variantslib.getVariantsClass)(comp.name,flavor);return componentCode=variants.length>0?(componentCode=componentCode.replace("{{VARIANTS}}",variants.join(" "))).replace("{{VARIANTSHTML}}",(0,_variantslib.getVariantsHtml)(comp.name,flavor)):(componentCode=componentCode.replace("{{VARIANTS}}","")).replace("{{VARIANTSHTML}}",""),componentCode=(componentCode=(componentCode=currentFlavor?componentCode.replace("{{FLAVOR}}","elements-"+currentFlavor+"-flavor"):componentCode.replace("{{FLAVOR}}","")).replace("{{COMPONENT}}","elements-"+comp.name)).replace("{{CATEGORY}}","elements-"+data.getCategoryById(currentCategoryId).name),componentCode=applyRandomID(componentCode),componentCode=applyLangStrings(componentCode)},handleButtonClick=async(event,editor,modal)=>{const selectedButton=event.target.closest("button").dataset.id,comp=data.getComponentById(selectedButton);if(comp){let componentCode=comp.code;const placeholder=selection.length>0?selection:comp.text;let flavor=comp.flavors.length>0?currentFlavor:"";const randomId=generateRandomID(),newNode=document.createElement("span");newNode.dataset.id=randomId,newNode.innerHTML=placeholder,componentCode=updateComponentCode(componentCode,selectedButton,newNode.outerHTML,flavor),editor.selection.setContent(componentCode);const nodeSel=editor.dom.select('span[data-id="'+randomId+'"]');null!=nodeSel&&nodeSel[0]&&editor.selection.select(nodeSel[0]),modal.destroy(),editor.focus()}},handleButtonMouseEvent=(event,modal,show)=>{const selectedButton=event.target.closest("button").dataset.id,node=modal.getRoot()[0].querySelector('div[data-id="code-preview-'+selectedButton+'"]'),previewDefault=modal.getRoot()[0].querySelector('div[data-id="code-preview-default"]'),comp=data.getComponentById(selectedButton);let flavor=comp.flavors.length>0?currentFlavor:"";const placeholder=selection.length>0?selection:comp.text;node.innerHTML=updateComponentCode(comp.code,selectedButton,placeholder,flavor),node&&(show?(previewDefault.classList.toggle("elements-hidden"),node.classList.toggle("elements-hidden")):(node.classList.toggle("elements-hidden"),previewDefault.classList.toggle("elements-hidden")))},handleVariantClick=(event,modal)=>{event.stopPropagation();const variant=event.target.closest("span"),button=event.target.closest("button"),comp=data.getComponentById(button.dataset.id),flavor=comp.flavors.length>0?currentFlavor:"";updateVariantComponentState(variant,button,modal,!1,!0);modal.getRoot()[0].querySelector('div[data-id="code-preview-'+button.dataset.id+'"]').innerHTML=updateComponentCode(comp.code,button.dataset.id,comp.text,flavor)},updateVariantComponentState=(variant,button,modal,show,updateHtml)=>{const selectedVariant=variant.dataset.variantclass,selectedButton=button.dataset.id,componentClass=button.dataset.classcomponent,previewComponent=modal.getRoot()[0].querySelector('div[data-id="code-preview-'+button.dataset.id+'"] .'+componentClass),variantPreview=modal.getRoot()[0].querySelector('span[data-id="variantHTML-'+button.dataset.id+'"]'),comp=data.getComponentById(selectedButton);let variantsHtml="",hasflavors=comp.flavors.length>0;previewComponent?updateHtml?("on"==variant.dataset.state?((0,_variantslib.removeVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!1),previewComponent.classList.remove(selectedVariant)):((0,_variantslib.addVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!0),previewComponent.classList.add(selectedVariant)),variantPreview&&(variantPreview.innerHTML=(0,_variantslib.getVariantsHtml)(comp.name,currentFlavor))):(variantsHtml=(0,_variantslib.getVariantsHtml)(comp.name,currentFlavor),show?(previewComponent.classList.add(selectedVariant),variantsHtml+=(0,_variantslib.getVariantHtml)(variant.dataset.variant)):previewComponent.classList.remove(selectedVariant),variantPreview&&(variantPreview.innerHTML=variantsHtml)):"on"==variant.dataset.state?((0,_variantslib.removeVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!1)):((0,_variantslib.addVariant)(comp.name,variant.dataset.variant,hasflavors?currentFlavor:""),updateVariantButtonState(variant,!0))},updateVariantButtonState=(variant,activate)=>{activate?(variant.dataset.state="on",variant.classList.remove(variant.dataset.variant+"-variant-off"),variant.classList.add(variant.dataset.variant+"-variant-on"),variant.classList.add("on")):(variant.dataset.state="off",variant.classList.remove(variant.dataset.variant+"-variant-on"),variant.classList.add(variant.dataset.variant+"-variant-off"),variant.classList.remove("on"))},showCategoryButtons=(modal,context)=>{const showNodes=modal.getRoot()[0].querySelectorAll('button[data-type="'+context+'"]'),hideNodes=modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type="'+context+'"])');showNodes.forEach((node=>node.classList.remove("elements-hidden"))),hideNodes.forEach((node=>node.classList.add("elements-hidden")))},applyLangStrings=text=>([...text.matchAll(/{{#([^}]*)}}/g)].forEach((strLang=>{text=text.replace("{{#"+strLang[1]+"}}",data.getLangString(strLang[1]))})),text),generateRandomID=()=>{const timestamp=(new Date).getTime();return"R"+Math.round(1e5*Math.random())+"-"+timestamp},applyRandomID=text=>{const compRegex=/{{@ID}}/g;return text.match(compRegex)&&(text=text.replace(compRegex,generateRandomID())),text}})); //# sourceMappingURL=ui.min.js.map \ No newline at end of file diff --git a/amd/build/ui.min.js.map b/amd/build/ui.min.js.map index 43584f3..d831b2d 100644 --- a/amd/build/ui.min.js.map +++ b/amd/build/ui.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L UI.\n *\n * @module tiny_c4l/ui\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {component} from './common';\nimport C4LModal from './modal';\nimport ModalFactory from 'core/modal_factory';\nimport {components as Components} from './components';\nimport {get_strings as getStrings} from 'core/str';\nimport {\n isStudent,\n getallowedComponents,\n showPreview,\n getpreviewCSS,\n getcustomComponents\n} from './options';\nimport ModalEvents from 'core/modal_events';\nimport {\n addVariant,\n getVariantsClass,\n getVariantHtml,\n getVariantsHtml,\n loadVariantPreferences,\n removeVariant,\n saveVariantPreferences,\n variantExists\n} from './variantslib';\n\nlet userStudent = false;\nlet previewC4L = true;\nlet allowedComponents = [];\nlet Contexts = [];\nlet langStrings = {};\nlet previewCSS = '';\nlet customComponents = [];\nconst compPrefix = 'c4lv-';\n\n/**\n * Handle action\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = async(editor) => {\n userStudent = isStudent(editor);\n previewC4L = showPreview(editor);\n customComponents = getcustomComponents(editor);\n addCustomComponents();\n allowedComponents = getallowedComponents(editor);\n previewCSS = getpreviewCSS(editor);\n langStrings = await getAllStrings();\n loadVariantPreferences(Components).then(() => displayDialogue(editor));\n};\n\n/**\n * Display modal\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n const data = Object.assign({}, {});\n\n // Show modal with buttons.\n const modal = await ModalFactory.create({\n type: C4LModal.TYPE,\n templateContext: await getTemplateContext(editor, data),\n large: true,\n });\n\n // Choose class to modal.\n const modalClass = previewC4L ? 'c4l-modal' : 'c4l-modal-no-preview';\n\n // Set class to modal.\n editor.targetElm.closest('body').classList.add(modalClass);\n\n // Inject custom component styles in editor.\n if (previewCSS !== \"\") {\n const styles = document.createElement('style');\n styles.textContent = previewCSS;\n editor.targetElm.closest('body').appendChild(styles);\n }\n\n modal.show();\n\n // Event modal listener.\n modal.getRoot().on(ModalEvents.hidden, () => {\n handleModalHidden(editor);\n });\n\n // Event filters listener.\n const filters = modal.getRoot()[0].querySelectorAll('.c4l-button-filter');\n filters.forEach(node => {\n node.addEventListener('click', (event) => {\n handleButtonFilterClick(event, modal);\n });\n });\n\n modal.getRoot()[0].querySelector('.c4l-select-filter').addEventListener('change', (event) => {\n handleSelectFilterChange(event, modal);\n });\n\n // Event buttons listeners.\n const buttons = modal.getRoot()[0].querySelectorAll('.c4lt-dialog-button');\n buttons.forEach(node => {\n node.addEventListener('click', (event) => {\n handleButtonClick(event, editor, modal);\n });\n if (previewC4L) {\n node.addEventListener('mouseenter', (event) => {\n handleButtonMouseEvent(event, modal, true);\n });\n node.addEventListener('mouseleave', (event) => {\n handleButtonMouseEvent(event, modal, false);\n });\n }\n });\n\n // Event variants listeners.\n const variants = modal.getRoot()[0].querySelectorAll('.c4l-button-variant');\n variants.forEach(node => {\n node.addEventListener('click', (event) => {\n handleVariantClick(event, modal);\n });\n if (previewC4L) {\n node.addEventListener('mouseenter', (event) => {\n handleVariantMouseEvent(event, modal, true);\n });\n node.addEventListener('mouseleave', (event) => {\n handleVariantMouseEvent(event, modal, false);\n });\n }\n });\n};\n\n/**\n * Handle a change within filter select.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleSelectFilterChange = (event, modal) => {\n const select = event.target.closest('select');\n\n if (select) {\n const currentContext = select.value;\n if (Contexts.indexOf(currentContext) !== -1) {\n // Select current button.\n const buttons = modal.getRoot()[0]\n .querySelectorAll('.c4l-buttons-filters button');\n buttons.forEach(node => node.classList.remove('c4l-button-filter-enabled'));\n const button = modal.getRoot()[0]\n .querySelector('.c4l-button-filter[data-filter=\"' + currentContext + '\"]');\n button.classList.add('c4l-button-filter-enabled');\n\n // Show/hide component buttons.\n showContextButtons(modal, currentContext);\n }\n }\n};\n\n/**\n * Handle a click within filter button.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleButtonFilterClick = (event, modal) => {\n const button = event.target.closest('button');\n\n const currentContext = button.dataset.filter;\n // Filter button.\n if (Contexts.indexOf(currentContext) !== -1) {\n // Select current button.\n const buttons = modal.getRoot()[0].querySelectorAll('.c4l-buttons-filters button');\n buttons.forEach(node => node.classList.remove('c4l-button-filter-enabled'));\n button.classList.add('c4l-button-filter-enabled');\n\n // Select current option in select.\n const select = modal.getRoot()[0].querySelector('.c4l-select-filter');\n select.selectedIndex = Contexts.indexOf(currentContext);\n\n // Show/hide component buttons.\n showContextButtons(modal, currentContext);\n }\n};\n\n/**\n * Handle when closing the Modal.\n *\n * @param {obj} editor\n */\nconst handleModalHidden = (editor) => {\n editor.targetElm.closest('body').classList.remove('c4l-modal-no-preview');\n saveVariantPreferences(Components);\n};\n\n/**\n * Handle a click in a component button.\n *\n * @param {MouseEvent} event The click event\n * @param {obj} editor\n * @param {obj} modal\n */\nconst handleButtonClick = (event, editor, modal) => {\n const selectedButton = event.target.closest('button').dataset.id;\n\n // Component button.\n const component = Components.find(element => element.name == selectedButton);\n if (component != undefined) {\n const sel = editor.selection.getContent();\n let componentCode = component.code;\n const placeholder = (sel.length > 0 ? sel : component.text);\n\n // Create a new node to replace the placeholder.\n const randomId = generateRandomID();\n const newNode = document.createElement('span');\n newNode.dataset.id = randomId;\n newNode.innerHTML = placeholder;\n componentCode = componentCode.replace('{{PLACEHOLDER}}', newNode.outerHTML);\n\n // Return active variants for current component.\n const variants = getVariantsClass(component.name);\n\n // Apply variants to html component.\n if (variants.length > 0) {\n componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));\n componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(component.name));\n } else {\n componentCode = componentCode.replace('{{VARIANTS}}', '');\n componentCode = componentCode.replace('{{VARIANTSHTML}}', '');\n }\n\n // Apply random IDs.\n componentCode = applyRandomID(componentCode);\n\n // Apply lang strings.\n componentCode = applyLangStrings(componentCode);\n\n // Sets new content.\n editor.selection.setContent(componentCode);\n\n // Select text.\n const nodeSel = editor.dom.select('span[data-id=\"' + randomId + '\"]');\n if (nodeSel?.[0]) {\n editor.selection.select(nodeSel[0]);\n }\n\n modal.destroy();\n editor.focus();\n }\n};\n\n/**\n * Handle a mouse events mouseenter/mouseleave in a component button.\n *\n * @param {MouseEvent} event The click event\n * @param {obj} modal\n * @param {bool} show\n */\nconst handleButtonMouseEvent = (event, modal, show) => {\n const selectedButton = event.target.closest('button').dataset.id;\n const node = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-' + selectedButton + '\"]');\n const previewDefault = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-default\"]');\n\n if (node) {\n if (show) {\n previewDefault.classList.toggle('c4l-hidden');\n node.classList.toggle('c4l-hidden');\n } else {\n node.classList.toggle('c4l-hidden');\n previewDefault.classList.toggle('c4l-hidden');\n }\n }\n};\n\n/**\n * Handle a mouse events mouseenter/mouseleave in a variant button.\n *\n * @param {MouseEvent} event The mouseenter/mouseleave event\n * @param {obj} modal\n * @param {bool} show\n */\nconst handleVariantMouseEvent = (event, modal, show) => {\n const variant = event.target.closest('span');\n const variantEnabled = variant.dataset.state == 'on';\n const button = event.target.closest('button');\n\n if (!variantEnabled) {\n updateVariantComponentState(variant, button, modal, show, false);\n }\n};\n\n\n/**\n * Handle a mouse event within the variant buttons.\n *\n * @param {MouseEvent} event The mouseenter/mouseleave event\n * @param {obj} modal\n */\nconst handleVariantClick = (event, modal) => {\n event.stopPropagation();\n const variant = event.target.closest('span');\n const button = event.target.closest('button');\n updateVariantComponentState(variant, button, modal, false, true);\n};\n\n/**\n * Get the template context for the dialogue.\n *\n * @param {Editor} editor\n * @param {object} data\n * @returns {object} data\n */\nconst getTemplateContext = async(editor, data) => {\n return Object.assign({}, {\n elementid: editor.id,\n buttons: await getButtons(editor),\n filters: await getFilters(),\n preview: previewC4L,\n }, data);\n};\n\n/**\n * Get the C4L filters for the dialogue.\n *\n * @returns {object} data\n */\nconst getFilters = async() => {\n const filters = [];\n const stringValues = await getStrings(Contexts.map((key) => ({key, component})));\n\n // Iterate over contexts.\n Contexts.forEach((context, index) => {\n filters.push({\n name: stringValues[index],\n type: context,\n filterClass: index === 0 ? 'c4l-button-filter-enabled' : '',\n });\n });\n\n return filters;\n};\n\n/**\n * Get the C4L buttons for the dialogue.\n *\n * @param {Editor} editor\n * @returns {object} buttons\n */\nconst getButtons = (editor) => {\n const buttons = [];\n const sel = editor.selection.getContent();\n let componentCode = '';\n let placeholder = '';\n let variants = [];\n let buttonText = '';\n\n // Iterate over components.\n Components.forEach((component) => {\n if (!userStudent || (userStudent && allowedComponents.includes(component.name))) {\n if (previewC4L) {\n placeholder = (sel.length > 0 ? sel : component.text);\n componentCode = component.code;\n componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder);\n // Return active variants for component.\n variants = getVariantsClass(component.name);\n\n // Apply class variants and html to html component.\n const variantsNode = document.createElement('span');\n variantsNode.dataset.id = 'variantHTML-' + component.id;\n if (variants.length > 0) {\n componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));\n variantsNode.innerHTML = getVariantsHtml(component.name);\n componentCode = componentCode.replace('{{VARIANTSHTML}}', variantsNode.outerHTML);\n } else {\n componentCode = componentCode.replace('{{VARIANTS}}', '');\n componentCode = componentCode.replace('{{VARIANTSHTML}}', variantsNode.outerHTML);\n }\n\n // Apply lang strings.\n componentCode = applyLangStrings(componentCode);\n }\n\n // Save contexts.\n if (Contexts.indexOf(component.type) === -1) {\n Contexts.push(component.type);\n }\n\n buttonText = component.type == 'custom' ? component.buttonname : langStrings.get(component.name);\n buttons.push({\n id: component.name,\n name: buttonText,\n type: component.type,\n icon: component.icon ?? '',\n imageClass: component.imageClass,\n classComponent: compPrefix + component.name,\n htmlcode: componentCode,\n css: component.css ?? '',\n variants: getVariantsState(component.name, component.variants),\n });\n\n // Add class to hide button.\n if (Contexts.indexOf(component.type) !== 0) {\n buttons[buttons.length - 1].imageClass += ' c4l-hidden';\n }\n }\n });\n\n return buttons;\n};\n\n/**\n * Get variants for the dialogue.\n *\n * @param {string} component\n * @param {object} elements\n * @return {object} Variants for a component\n */\nconst getVariantsState = (component, elements) => {\n const variants = [];\n let variantState = '';\n let variantClass = '';\n\n // Max 3 variants.\n if (elements.length > 3) {\n elements = elements.slice(0, 2);\n }\n\n elements.forEach((variant, index) => {\n if (variantExists(component, variant)) {\n variantState = 'on';\n variantClass = 'on ';\n } else {\n variantState = 'off';\n variantClass = '';\n }\n variantClass += variant + '-variant-' + variantState;\n variants.push({\n id: index,\n name: variant,\n state: variantState,\n imageClass: variantClass,\n title: langStrings.get(variant),\n });\n });\n\n return variants;\n};\n\n/**\n * Update a variant component UI.\n *\n * @param {obj} variant\n * @param {obj} button\n * @param {obj} modal\n * @param {bool} show\n * @param {bool} updateHtml\n */\nconst updateVariantComponentState = (variant, button, modal, show, updateHtml) => {\n const selectedVariant = 'c4l-' + variant.dataset.variant + '-variant';\n const component = Components.find(element => element.name == button.dataset.id);\n const componentClass = button.dataset.classcomponent;\n const previewComponent = modal.getRoot()[0]\n .querySelector('div[data-id=\"code-preview-' + button.dataset.id + '\"] .' + componentClass);\n const variantPreview = modal.getRoot()[0]\n .querySelector('span[data-id=\"variantHTML-' + button.dataset.id + '\"]');\n let variantsHtml = '';\n\n if (previewComponent) {\n if (updateHtml) {\n if (variant.dataset.state == 'on') {\n removeVariant(component.name, variant.dataset.variant);\n updateVariantButtonState(variant, false);\n previewComponent.classList.remove(selectedVariant);\n } else {\n addVariant(component.name, variant.dataset.variant);\n updateVariantButtonState(variant, true);\n previewComponent.classList.add(selectedVariant);\n }\n\n // Update variant preview HTML.\n if (variantPreview) {\n variantPreview.innerHTML = getVariantsHtml(component.name);\n }\n } else {\n variantsHtml = getVariantsHtml(component.name);\n if (show) {\n previewComponent.classList.add(selectedVariant);\n variantsHtml += getVariantHtml(variant.dataset.variant);\n } else {\n previewComponent.classList.remove(selectedVariant);\n }\n\n // Update variant preview HTML.\n if (variantPreview) {\n variantPreview.innerHTML = variantsHtml;\n }\n }\n } else {\n if (updateHtml) {\n // Update variants preferences.\n if (variant.dataset.state == 'on') {\n removeVariant(component.name, variant.dataset.variant);\n updateVariantButtonState(variant, false);\n } else {\n addVariant(component.name, variant.dataset.variant);\n updateVariantButtonState(variant, true);\n }\n }\n }\n};\n\n/**\n * Update a variant button UI.\n *\n * @param {obj} variant\n * @param {bool} activate\n */\nconst updateVariantButtonState = (variant, activate) => {\n if (activate) {\n variant.dataset.state = 'on';\n variant.classList.remove(variant.dataset.variant + '-variant-off');\n variant.classList.add(variant.dataset.variant + '-variant-on');\n variant.classList.add('on');\n } else {\n variant.dataset.state = 'off';\n variant.classList.remove(variant.dataset.variant + '-variant-on');\n variant.classList.add(variant.dataset.variant + '-variant-off');\n variant.classList.remove('on');\n }\n};\n\n/**\n * Show/hide buttons depend on selected context.\n *\n * @param {object} modal\n * @param {String} context\n */\nconst showContextButtons = (modal, context) => {\n const showNodes = modal.getRoot()[0].querySelectorAll('button[data-type=\"' + context + '\"]');\n const hideNodes = modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type=\"' + context + '\"])');\n\n showNodes.forEach(node => node.classList.remove('c4l-hidden'));\n hideNodes.forEach(node => node.classList.add('c4l-hidden'));\n};\n\n/**\n * Replace all localized strings.\n *\n * @param {String} text\n * @return {String} String with lang tags replaced with a localized string.\n */\nconst applyLangStrings = (text) => {\n const compRegex = /{{#([^}]*)}}/g;\n\n [...text.matchAll(compRegex)].forEach(strLang => {\n text = text.replace('{{#' + strLang[1] + '}}', langStrings.get(strLang[1]));\n });\n\n return text;\n};\n\n/**\n * Generates a random string.\n * @return {string} A random string\n */\nconst generateRandomID = () => {\n const timestamp = new Date().getTime();\n return 'R' + Math.round(Math.random() * 100000) + '-' + timestamp;\n};\n\n/**\n * Replace all ID tags with a random string.\n * @param {String} text\n * @return {String} String with all ID tags replaced with a random string.\n */\nconst applyRandomID = (text) => {\n const compRegex = /{{@ID}}/g;\n\n if (text.match(compRegex)) {\n text = text.replace(compRegex, generateRandomID());\n }\n\n return text;\n};\n\n/**\n * Get language strings.\n *\n * @return {object} Language strings\n */\nconst getAllStrings = async() => {\n const keys = [];\n const compRegex = /{{#([^}]*)}}/g;\n\n Components.forEach(element => {\n\n // Only add name from standard components.\n if (element.name.indexOf(\"customcomp\") == -1) {\n keys.push(element.name);\n }\n\n // Get lang strings from variants.\n element.variants.forEach(variant => {\n if (keys.indexOf(variant) === -1) {\n keys.push(variant);\n }\n });\n\n // Get lang strings from components.\n [...element.code.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n\n // Get lang strings from text placeholders.\n [...element.text.matchAll(compRegex)].forEach(strLang => {\n if (keys.indexOf(strLang[1]) === -1) {\n keys.push(strLang[1]);\n }\n });\n });\n\n const stringValues = await getStrings(keys.map((key) => ({key, component})));\n return new Map(keys.map((key, index) => ([key, stringValues[index]])));\n};\n\n/**\n * Add custom components to standard components.\n */\nconst addCustomComponents = () => {\n if (customComponents.length > 0) {\n customComponents.forEach(customcomp => {\n if (Components.find(element => element.id == customcomp['id'] + 1000) == undefined) {\n Components.push({\n id: customcomp['id'] + 1000,\n name: customcomp['name'],\n buttonname: customcomp['buttonname'],\n type: 'custom',\n imageClass: 'c4l-custom-icon',\n code: replaceCustomPlaceholders(customcomp),\n text: customcomp['text'].length > 0 ? customcomp['text'] : '{{#textplaceholder}}',\n variants: customcomp['variants'] ? [\"full-width\"] : [],\n icon: customcomp['icon'],\n css: customcomp['css']\n });\n }\n });\n }\n};\n\n/**\n * Replace custom placeholders with values.\n *\n * @param {object} component\n * @return {string} HTML code.\n */\nconst replaceCustomPlaceholders = (component) => {\n let html = component['code'];\n const variants = component['variants'] ? \" {{VARIANTS}}\" : \"\";\n html = html.replace('{{CUSTOMCLASS}}', compPrefix + component['name'] + ' ' + compPrefix + \"custom-component\" + variants);\n\n return html;\n};\n"],"names":["_interopRequireDefault","obj","__esModule","default","_modal","_modal_factory","_modal_events","userStudent","previewC4L","allowedComponents","Contexts","langStrings","previewCSS","customComponents","_exports","handleAction","async","isStudent","editor","showPreview","getcustomComponents","addCustomComponents","getallowedComponents","getpreviewCSS","getAllStrings","loadVariantPreferences","Components","components","then","displayDialogue","data","Object","assign","modal","ModalFactory","create","type","C4LModal","TYPE","templateContext","getTemplateContext","large","modalClass","targetElm","closest","classList","add","styles","document","createElement","textContent","appendChild","show","getRoot","on","ModalEvents","hidden","handleModalHidden","querySelectorAll","forEach","node","addEventListener","event","handleButtonFilterClick","querySelector","handleSelectFilterChange","handleButtonClick","handleButtonMouseEvent","handleVariantClick","handleVariantMouseEvent","select","target","currentContext","value","indexOf","remove","showContextButtons","button","dataset","filter","selectedIndex","saveVariantPreferences","selectedButton","id","component","find","element","name","undefined","sel","selection","getContent","componentCode","code","placeholder","length","text","randomId","generateRandomID","newNode","innerHTML","replace","outerHTML","variants","getVariantsClass","join","getVariantsHtml","applyRandomID","applyLangStrings","setContent","nodeSel","dom","destroy","focus","previewDefault","toggle","variant","variantEnabled","state","updateVariantComponentState","stopPropagation","elementid","buttons","getButtons","filters","getFilters","preview","stringValues","getStrings","map","key","context","index","push","filterClass","buttonText","includes","variantsNode","buttonname","get","icon","imageClass","classComponent","htmlcode","css","getVariantsState","elements","variantState","variantClass","slice","variantExists","title","updateHtml","selectedVariant","componentClass","classcomponent","previewComponent","variantPreview","variantsHtml","removeVariant","updateVariantButtonState","addVariant","getVariantHtml","activate","showNodes","hideNodes","matchAll","strLang","timestamp","Date","getTime","Math","round","random","compRegex","match","keys","Map","customcomp","replaceCustomPlaceholders","html","compPrefix"],"mappings":"4PAmC4C,SAAAA,uBAAAC,KAAAA,OAAAA,KAAAA,IAAAC,WAAAD,IAAAE,CAAAA,QAAAF,IAAA;;;;;;;0FAX5CG,OAAAJ,uBAAAI,QACAC,eAAAL,uBAAAK,gBAUAC,cAAAN,uBAAAM,eAYA,IAAIC,aAAc,EACdC,YAAa,EACbC,kBAAoB,GACpBC,SAAW,GACXC,YAAc,CAAA,EACdC,WAAa,GACbC,iBAAmB,GAiBrBC,SAAAC,aAT0BC,eACxBT,aAAc,EAAAU,SAASA,WAACC,QACxBV,YAAa,EAAAW,SAAWA,aAACD,QACzBL,kBAAmB,EAAAO,SAAmBA,qBAACF,QACvCG,sBACAZ,mBAAoB,EAAAa,SAAoBA,sBAACJ,QACzCN,YAAa,EAAAW,SAAaA,eAACL,QAC3BP,kBAAoBa,iBACpB,EAAAC,aAAsBA,wBAACC,YAAUC,YAAEC,MAAK,IAAMC,gBAAgBX,SAAQ,EAQ1E,MAAMW,gBAAkBb,eACpB,MAAMc,KAAOC,OAAOC,OAAO,CAAE,EAAE,CAAE,GAG3BC,YAAcC,eAAY/B,QAACgC,OAAO,CACpCC,KAAMC,OAAQlC,QAACmC,KACfC,sBAAuBC,mBAAmBtB,OAAQY,MAClDW,OAAO,IAILC,WAAalC,WAAa,YAAc,uBAM9C,GAHAU,OAAOyB,UAAUC,QAAQ,QAAQC,UAAUC,IAAIJ,YAG5B,KAAf9B,WAAmB,CACnB,MAAMmC,OAASC,SAASC,cAAc,SACtCF,OAAOG,YAActC,WACrBM,OAAOyB,UAAUC,QAAQ,QAAQO,YAAYJ,OACjD,CAEAd,MAAMmB,OAGNnB,MAAMoB,UAAUC,GAAGC,cAAWpD,QAACqD,QAAQ,KACnCC,kBAAkBvC,OAAO,IAIbe,MAAMoB,UAAU,GAAGK,iBAAiB,sBAC5CC,SAAQC,OACZA,KAAKC,iBAAiB,SAAUC,QAC5BC,wBAAwBD,MAAO7B,MAAM,GACvC,IAGNA,MAAMoB,UAAU,GAAGW,cAAc,sBAAsBH,iBAAiB,UAAWC,QAChFG,yBAAyBH,MAAO7B,MAAM,IAIzBA,MAAMoB,UAAU,GAAGK,iBAAiB,uBAC5CC,SAAQC,OACZA,KAAKC,iBAAiB,SAAUC,QAC5BI,kBAAkBJ,MAAO5C,OAAQe,MAAM,IAEvCzB,aACAoD,KAAKC,iBAAiB,cAAeC,QACjCK,uBAAuBL,MAAO7B,OAAO,EAAK,IAE9C2B,KAAKC,iBAAiB,cAAeC,QACjCK,uBAAuBL,MAAO7B,OAAO,EAAM,IAEnD,IAIaA,MAAMoB,UAAU,GAAGK,iBAAiB,uBAC5CC,SAAQC,OACbA,KAAKC,iBAAiB,SAAUC,QAC5BM,mBAAmBN,MAAO7B,MAAM,IAEhCzB,aACAoD,KAAKC,iBAAiB,cAAeC,QACjCO,wBAAwBP,MAAO7B,OAAO,EAAK,IAE/C2B,KAAKC,iBAAiB,cAAeC,QACjCO,wBAAwBP,MAAO7B,OAAO,EAAM,IAEpD,GACF,EASAgC,yBAA2BA,CAACH,MAAO7B,SACrC,MAAMqC,OAASR,MAAMS,OAAO3B,QAAQ,UAEpC,GAAI0B,OAAQ,CACR,MAAME,eAAiBF,OAAOG,MAC9B,IAA0C,IAAtC/D,SAASgE,QAAQF,gBAAwB,CAEzBvC,MAAMoB,UAAU,GAC3BK,iBAAiB,+BACdC,SAAQC,MAAQA,KAAKf,UAAU8B,OAAO,+BAC/B1C,MAAMoB,UAAU,GAC1BW,cAAc,mCAAqCQ,eAAiB,MAClE3B,UAAUC,IAAI,6BAGrB8B,mBAAmB3C,MAAOuC,eAC9B,CACJ,GASET,wBAA0BA,CAACD,MAAO7B,SACpC,MAAM4C,OAASf,MAAMS,OAAO3B,QAAQ,UAE9B4B,eAAiBK,OAAOC,QAAQC,OAEtC,IAA0C,IAAtCrE,SAASgE,QAAQF,gBAAwB,CAEzBvC,MAAMoB,UAAU,GAAGK,iBAAiB,+BAC5CC,SAAQC,MAAQA,KAAKf,UAAU8B,OAAO,+BAC9CE,OAAOhC,UAAUC,IAAI,6BAGNb,MAAMoB,UAAU,GAAGW,cAAc,sBACzCgB,cAAgBtE,SAASgE,QAAQF,gBAGxCI,mBAAmB3C,MAAOuC,eAC9B,GAQEf,kBAAqBvC,SACvBA,OAAOyB,UAAUC,QAAQ,QAAQC,UAAU8B,OAAO,yBAClD,EAAAM,aAAAA,wBAAuBvD,YAAAA,WAAW,EAUhCwC,kBAAoBA,CAACJ,MAAO5C,OAAQe,SACtC,MAAMiD,eAAiBpB,MAAMS,OAAO3B,QAAQ,UAAUkC,QAAQK,GAGxDC,UAAY1D,YAAUC,WAAC0D,MAAKC,SAAWA,QAAQC,MAAQL,iBAC7D,GAAiBM,MAAbJ,UAAwB,CACxB,MAAMK,IAAMvE,OAAOwE,UAAUC,aAC7B,IAAIC,cAAgBR,UAAUS,KAC9B,MAAMC,YAAeL,IAAIM,OAAS,EAAIN,IAAML,UAAUY,KAGhDC,SAAWC,mBACXC,QAAUnD,SAASC,cAAc,QACvCkD,QAAQrB,QAAQK,GAAKc,SACrBE,QAAQC,UAAYN,YACpBF,cAAgBA,cAAcS,QAAQ,kBAAmBF,QAAQG,WAGjE,MAAMC,UAAW,EAAAC,aAAAA,kBAAiBpB,UAAUG,MAGxCgB,SAASR,OAAS,GAClBH,cAAgBA,cAAcS,QAAQ,eAAgBE,SAASE,KAAK,MACpEb,cAAgBA,cAAcS,QAAQ,oBAAoB,EAAAK,aAAAA,iBAAgBtB,UAAUG,SAEpFK,cAAgBA,cAAcS,QAAQ,eAAgB,IACtDT,cAAgBA,cAAcS,QAAQ,mBAAoB,KAI9DT,cAAgBe,cAAcf,eAG9BA,cAAgBgB,iBAAiBhB,eAGjC1E,OAAOwE,UAAUmB,WAAWjB,eAG5B,MAAMkB,QAAU5F,OAAO6F,IAAIzC,OAAO,iBAAmB2B,SAAW,MAC5Da,UAAU,IACV5F,OAAOwE,UAAUpB,OAAOwC,QAAQ,IAGpC7E,MAAM+E,UACN9F,OAAO+F,OACX,GAUE9C,uBAAyBA,CAACL,MAAO7B,MAAOmB,QAC1C,MAAM8B,eAAiBpB,MAAMS,OAAO3B,QAAQ,UAAUkC,QAAQK,GACxDvB,KAAO3B,MAAMoB,UAAU,GAAGW,cAAc,6BAA+BkB,eAAiB,MACxFgC,eAAiBjF,MAAMoB,UAAU,GAAGW,cAAc,uCAEpDJ,OACIR,MACA8D,eAAerE,UAAUsE,OAAO,cAChCvD,KAAKf,UAAUsE,OAAO,gBAEtBvD,KAAKf,UAAUsE,OAAO,cACtBD,eAAerE,UAAUsE,OAAO,eAExC,EAUE9C,wBAA0BA,CAACP,MAAO7B,MAAOmB,QAC3C,MAAMgE,QAAUtD,MAAMS,OAAO3B,QAAQ,QAC/ByE,eAA0C,MAAzBD,QAAQtC,QAAQwC,MACjCzC,OAASf,MAAMS,OAAO3B,QAAQ,UAE/ByE,gBACDE,4BAA4BH,QAASvC,OAAQ5C,MAAOmB,MAAM,EAC9D,EAUEgB,mBAAqBA,CAACN,MAAO7B,SAC/B6B,MAAM0D,kBACN,MAAMJ,QAAUtD,MAAMS,OAAO3B,QAAQ,QAC/BiC,OAASf,MAAMS,OAAO3B,QAAQ,UACpC2E,4BAA4BH,QAASvC,OAAQ5C,OAAO,GAAO,EAAK,EAU9DO,mBAAqBxB,MAAME,OAAQY,OAC9BC,OAAOC,OAAO,GAAI,CACrByF,UAAWvG,OAAOiE,GAClBuC,cAAeC,WAAWzG,QAC1B0G,cAAeC,aACfC,QAAStH,YACVsB,MAQD+F,WAAa7G,UACf,MAAM4G,QAAU,GACVG,mBAAqB,EAAAC,KAAAA,aAAWtH,SAASuH,KAAKC,MAAS,CAACA,QAAK9C,UAAAA,QAAAA,eAWnE,OARA1E,SAASiD,SAAQ,CAACwE,QAASC,SACvBR,QAAQS,KAAK,CACT9C,KAAMwC,aAAaK,OACnBhG,KAAM+F,QACNG,YAAuB,IAAVF,MAAc,4BAA8B,IAC3D,IAGCR,OAAO,EASZD,WAAczG,SAChB,MAAMwG,QAAU,GACVjC,IAAMvE,OAAOwE,UAAUC,aAC7B,IAAIC,cAAgB,GAChBE,YAAc,GACdS,SAAW,GACXgC,WAAa,GAqDjB,OAlDA7G,YAAAA,WAAWiC,SAASyB,YAChB,IAAK7E,aAAgBA,aAAeE,kBAAkB+H,SAASpD,UAAUG,MAAQ,CAC7E,GAAI/E,WAAY,CACZsF,YAAeL,IAAIM,OAAS,EAAIN,IAAML,UAAUY,KAChDJ,cAAgBR,UAAUS,KAC1BD,cAAgBA,cAAcS,QAAQ,kBAAmBP,aAEzDS,UAAW,EAAAC,aAAAA,kBAAiBpB,UAAUG,MAGtC,MAAMkD,aAAezF,SAASC,cAAc,QAC5CwF,aAAa3D,QAAQK,GAAK,eAAiBC,UAAUD,GACjDoB,SAASR,OAAS,GAClBH,cAAgBA,cAAcS,QAAQ,eAAgBE,SAASE,KAAK,MACpEgC,aAAarC,WAAY,EAAAM,8BAAgBtB,UAAUG,MACnDK,cAAgBA,cAAcS,QAAQ,mBAAoBoC,aAAanC,aAEvEV,cAAgBA,cAAcS,QAAQ,eAAgB,IACtDT,cAAgBA,cAAcS,QAAQ,mBAAoBoC,aAAanC,YAI3EV,cAAgBgB,iBAAiBhB,cACrC,EAG0C,IAAtClF,SAASgE,QAAQU,UAAUhD,OAC3B1B,SAAS2H,KAAKjD,UAAUhD,MAG5BmG,WAA+B,UAAlBnD,UAAUhD,KAAmBgD,UAAUsD,WAAa/H,YAAYgI,IAAIvD,UAAUG,MAC3FmC,QAAQW,KAAK,CACTlD,GAAIC,UAAUG,KACdA,KAAMgD,WACNnG,KAAMgD,UAAUhD,KAChBwG,KAAMxD,UAAUwD,MAAQ,GACxBC,WAAYzD,UAAUyD,WACtBC,eAvWG,QAuW0B1D,UAAUG,KACvCwD,SAAUnD,cACVoD,IAAK5D,UAAU4D,KAAO,GACtBzC,SAAU0C,iBAAiB7D,UAAUG,KAAMH,UAAUmB,YAIhB,IAArC7F,SAASgE,QAAQU,UAAUhD,QAC3BsF,QAAQA,QAAQ3B,OAAS,GAAG8C,YAAc,cAElD,KAGGnB,OAAO,EAUZuB,iBAAmBA,CAAC7D,UAAW8D,YACjC,MAAM3C,SAAW,GACjB,IAAI4C,aAAe,GACfC,aAAe,GAyBnB,OAtBIF,SAASnD,OAAS,IAClBmD,SAAWA,SAASG,MAAM,EAAG,IAGjCH,SAASvF,SAAQ,CAACyD,QAASgB,UACnB,EAAAkB,aAAaA,eAAClE,UAAWgC,UACzB+B,aAAe,KACfC,aAAe,QAEfD,aAAe,MACfC,aAAe,IAEnBA,cAAgBhC,QAAU,YAAc+B,aACxC5C,SAAS8B,KAAK,CACVlD,GAAIiD,MACJ7C,KAAM6B,QACNE,MAAO6B,aACPN,WAAYO,aACZG,MAAO5I,YAAYgI,IAAIvB,UACzB,IAGCb,QAAQ,EAYbgB,4BAA8BA,CAACH,QAASvC,OAAQ5C,MAAOmB,KAAMoG,cAC/D,MAAMC,gBAAkB,OAASrC,QAAQtC,QAAQsC,QAAU,WACrDhC,UAAY1D,YAAAA,WAAW2D,MAAKC,SAAWA,QAAQC,MAAQV,OAAOC,QAAQK,KACtEuE,eAAiB7E,OAAOC,QAAQ6E,eAChCC,iBAAmB3H,MAAMoB,UAAU,GACpCW,cAAc,6BAA+Ba,OAAOC,QAAQK,GAAK,OAASuE,gBACzEG,eAAiB5H,MAAMoB,UAAU,GAClCW,cAAc,6BAA+Ba,OAAOC,QAAQK,GAAK,MACtE,IAAI2E,aAAe,GAEfF,iBACIJ,YAC6B,MAAzBpC,QAAQtC,QAAQwC,QAChB,EAAAyC,aAAAA,eAAc3E,UAAUG,KAAM6B,QAAQtC,QAAQsC,SAC9C4C,yBAAyB5C,SAAS,GAClCwC,iBAAiB/G,UAAU8B,OAAO8E,oBAElC,EAAAQ,aAAAA,YAAW7E,UAAUG,KAAM6B,QAAQtC,QAAQsC,SAC3C4C,yBAAyB5C,SAAS,GAClCwC,iBAAiB/G,UAAUC,IAAI2G,kBAI/BI,iBACAA,eAAezD,WAAY,EAAAM,8BAAgBtB,UAAUG,SAGzDuE,cAAe,EAAApD,aAAAA,iBAAgBtB,UAAUG,MACrCnC,MACAwG,iBAAiB/G,UAAUC,IAAI2G,iBAC/BK,eAAgB,EAAAI,aAAcA,gBAAC9C,QAAQtC,QAAQsC,UAE/CwC,iBAAiB/G,UAAU8B,OAAO8E,iBAIlCI,iBACAA,eAAezD,UAAY0D,eAI/BN,aAE6B,MAAzBpC,QAAQtC,QAAQwC,QAChB,EAAAyC,aAAAA,eAAc3E,UAAUG,KAAM6B,QAAQtC,QAAQsC,SAC9C4C,yBAAyB5C,SAAS,MAElC,EAAA6C,aAAAA,YAAW7E,UAAUG,KAAM6B,QAAQtC,QAAQsC,SAC3C4C,yBAAyB5C,SAAS,IAG9C,EASE4C,yBAA2BA,CAAC5C,QAAS+C,YACnCA,UACA/C,QAAQtC,QAAQwC,MAAQ,KACxBF,QAAQvE,UAAU8B,OAAOyC,QAAQtC,QAAQsC,QAAU,gBACnDA,QAAQvE,UAAUC,IAAIsE,QAAQtC,QAAQsC,QAAU,eAChDA,QAAQvE,UAAUC,IAAI,QAEtBsE,QAAQtC,QAAQwC,MAAQ,MACxBF,QAAQvE,UAAU8B,OAAOyC,QAAQtC,QAAQsC,QAAU,eACnDA,QAAQvE,UAAUC,IAAIsE,QAAQtC,QAAQsC,QAAU,gBAChDA,QAAQvE,UAAU8B,OAAO,MAC7B,EASEC,mBAAqBA,CAAC3C,MAAOkG,WAC/B,MAAMiC,UAAYnI,MAAMoB,UAAU,GAAGK,iBAAiB,qBAAuByE,QAAU,MACjFkC,UAAYpI,MAAMoB,UAAU,GAAGK,iBAAiB,qCAAuCyE,QAAU,OAEvGiC,UAAUzG,SAAQC,MAAQA,KAAKf,UAAU8B,OAAO,gBAChD0F,UAAU1G,SAAQC,MAAQA,KAAKf,UAAUC,IAAI,eAAc,EASzD8D,iBAAoBZ,OAGtB,IAAIA,KAAKsE,SAFS,kBAEY3G,SAAQ4G,UAClCvE,KAAOA,KAAKK,QAAQ,MAAQkE,QAAQ,GAAK,KAAM5J,YAAYgI,IAAI4B,QAAQ,IAAI,IAGxEvE,MAOLE,iBAAmBA,KACrB,MAAMsE,WAAY,IAAIC,MAAOC,UAC7B,MAAO,IAAMC,KAAKC,MAAsB,IAAhBD,KAAKE,UAAqB,IAAML,SAAS,EAQ/D7D,cAAiBX,OACnB,MAAM8E,UAAY,WAMlB,OAJI9E,KAAK+E,MAAMD,aACX9E,KAAOA,KAAKK,QAAQyE,UAAW5E,qBAG5BF,IAAI,EAQTxE,cAAgBR,UAClB,MAAMgK,KAAO,GACPF,UAAY,gBAElBpJ,YAAAA,WAAWiC,SAAQ2B,WAG4B,GAAvCA,QAAQC,KAAKb,QAAQ,eACrBsG,KAAK3C,KAAK/C,QAAQC,MAItBD,QAAQiB,SAAS5C,SAAQyD,WACU,IAA3B4D,KAAKtG,QAAQ0C,UACb4D,KAAK3C,KAAKjB,QACd,IAIJ,IAAI9B,QAAQO,KAAKyE,SAASQ,YAAYnH,SAAQ4G,WACR,IAA9BS,KAAKtG,QAAQ6F,QAAQ,KACrBS,KAAK3C,KAAKkC,QAAQ,GACtB,IAIJ,IAAIjF,QAAQU,KAAKsE,SAASQ,YAAYnH,SAAQ4G,WACR,IAA9BS,KAAKtG,QAAQ6F,QAAQ,KACrBS,KAAK3C,KAAKkC,QAAQ,GACtB,GACF,IAGN,MAAMxC,mBAAqB,EAAAC,KAAAA,aAAWgD,KAAK/C,KAAKC,MAAS,CAACA,QAAK9C,UAAAA,QAAAA,eAC/D,OAAO,IAAI6F,IAAID,KAAK/C,KAAI,CAACC,IAAKE,QAAW,CAACF,IAAKH,aAAaK,UAAU,EAMpE/G,oBAAsBA,KACpBR,iBAAiBkF,OAAS,GAC1BlF,iBAAiB8C,SAAQuH,aACoD1F,MAArE9D,uBAAW2D,MAAKC,SAAWA,QAAQH,IAAM+F,WAAe,GAAI,OAC5DxJ,YAAUC,WAAC0G,KAAK,CACZlD,GAAI+F,WAAe,GAAI,IACvB3F,KAAM2F,WAAiB,KACvBxC,WAAYwC,WAAuB,WACnC9I,KAAM,SACNyG,WAAY,kBACZhD,KAAMsF,0BAA0BD,YAChClF,KAAMkF,WAAiB,KAAEnF,OAAS,EAAImF,WAAiB,KAAI,uBAC3D3E,SAAU2E,WAAqB,SAAI,CAAC,cAAgB,GACpDtC,KAAMsC,WAAiB,KACvBlC,IAAKkC,WAAgB,KAE7B,GAER,EASEC,0BAA6B/F,YAC/B,IAAIgG,KAAOhG,UAAgB,KAC3B,MAAMmB,SAAWnB,UAAoB,SAAI,gBAAkB,GAG3D,OAFAgG,KAAOA,KAAK/E,QAAQ,kBAjnBL,QAinBqCjB,UAAgB,KAA7BiG,yBAAyE9E,UAEzG6E,IAAI,CACb"} \ No newline at end of file +{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny Elements UI.\n *\n * @module tiny_elements/ui\n * @copyright 2022 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {ElementsModal} from 'tiny_elements/modal';\nimport {\n isStudent,\n showPreview,\n canManage\n} from 'tiny_elements/options';\nimport ModalEvents from 'core/modal_events';\nimport {\n addVariant,\n getVariantsClass,\n getVariantHtml,\n getVariantPreferences,\n getVariantsHtml,\n loadVariantPreferences,\n removeVariant,\n setData as setVariantsData\n} from 'tiny_elements/variantslib';\nimport {\n savePreferences,\n loadPreferences,\n Preferences\n} from 'tiny_elements/preferencelib';\nimport {getContextId} from 'editor_tiny/options';\nimport Data from 'tiny_elements/data';\n\nlet currentFlavor = '';\nlet currentFlavorId = 0;\nlet currentCategoryId = 1;\nlet currentCategoryName = '';\nlet lastFlavor = [];\nlet selection = '';\nlet data = {};\n\n/**\n * Handle action\n *\n * @param {TinyMCE} editor\n */\nexport const handleAction = async(editor) => {\n selection = editor.selection.getContent();\n data = new Data(\n getContextId(editor),\n isStudent(editor),\n showPreview(editor),\n canManage(editor)\n );\n await data.loadData();\n setVariantsData(data);\n\n currentCategoryId = loadPreferences(Preferences.category);\n lastFlavor = loadPreferences(Preferences.category_flavors);\n if (lastFlavor === null) {\n lastFlavor = [];\n }\n let componentVariants = loadPreferences(Preferences.component_variants);\n if (componentVariants === null) {\n componentVariants = {};\n }\n loadVariantPreferences(componentVariants);\n await displayDialogue(editor);\n};\n\n/**\n * Display modal\n *\n * @param {TinyMCE} editor\n */\nconst displayDialogue = async(editor) => {\n const templateContext = data.getTemplateContext(editor);\n // Show modal with buttons.\n const modal = await ElementsModal.create({\n type: ElementsModal.TYPE,\n templateContext: templateContext,\n large: true,\n });\n\n // Choose class to modal.\n const modalClass = data.getPreviewElements() ? 'elements-modal' : 'elements-modal-no-preview';\n\n // Set class to modal.\n editor.targetElm.closest('body').classList.add(modalClass);\n\n modal.show();\n\n // Event modal listener.\n modal.getRoot().on(ModalEvents.hidden, () => {\n handleModalHidden(editor);\n });\n\n // Event listener for categories without flavors.\n const soleCategories = modal.getRoot()[0].querySelectorAll('.elements-category.no-flavors');\n soleCategories.forEach(node => {\n node.addEventListener('click', (event) => {\n handleCategoryClick(event, modal);\n });\n });\n\n // Event listener for categories with flavors.\n const selectCategories = modal.getRoot()[0].querySelectorAll('.elements-category-flavor');\n selectCategories.forEach(node => {\n node.addEventListener('click', (event) => {\n handleCategoryFlavorClick(event, modal);\n });\n });\n\n // Event listener for category dropdown, triggering to switch to last used flavor.\n const selectCategoriesRemember = modal.getRoot()[0].querySelectorAll('.nav-link.dropdown-toggle');\n selectCategoriesRemember.forEach(node => {\n node.addEventListener('click', (event) => {\n handleCategoryRemember(event, modal);\n });\n });\n\n // Event buttons listeners.\n const buttons = modal.getRoot()[0].querySelectorAll('.elementst-dialog-button');\n buttons.forEach(node => {\n node.addEventListener('click', (event) => {\n handleButtonClick(event, editor, modal);\n });\n if (data.getPreviewElements()) {\n node.addEventListener('mouseenter', (event) => {\n handleButtonMouseEvent(event, modal, true);\n });\n node.addEventListener('mouseleave', (event) => {\n handleButtonMouseEvent(event, modal, false);\n });\n }\n });\n\n // Event variants listeners.\n const variants = modal.getRoot()[0].querySelectorAll('.elements-button-variant');\n variants.forEach(node => {\n node.addEventListener('click', (event) => {\n handleVariantClick(event, modal);\n });\n });\n\n // Select first or saved category.\n if (soleCategories.length > 0 || selectCategories.length > 0) {\n const savedCategory = currentCategoryId;\n const savedFlavor = lastFlavor[currentCategoryId];\n if (soleCategories.length == 0 || soleCategories[0].displayorder > selectCategories[0].displayorder) {\n selectCategories[0].click();\n } else {\n soleCategories[0].click();\n }\n if (savedCategory != 0) {\n soleCategories.forEach((node) => {\n if (node.dataset.categoryid == savedCategory) {\n node.click();\n }\n });\n selectCategories.forEach((node) => {\n if (node.dataset.categoryid == savedCategory) {\n node.click();\n }\n });\n if (savedFlavor) {\n const flavorlink = modal.getRoot()[0].querySelector(\n '.elements-category-flavor[data-id=\"' + savedFlavor + '\"]'\n );\n if (flavorlink) {\n flavorlink.click();\n }\n }\n }\n }\n};\n\n/**\n * Handle a click within filter button.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleCategoryClick = (event, modal) => {\n const link = event.target;\n currentCategoryId = link.dataset.categoryid;\n currentCategoryName = link.dataset.categoryname;\n\n // Remove active from all and set to selected.\n const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item');\n links.forEach(node => node.classList.remove('active'));\n link.classList.add('active');\n\n // Show/hide component buttons.\n showCategoryButtons(modal, currentCategoryName);\n};\n\n/**\n * Handle a click on a flavor in the category dropdown.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleCategoryFlavorClick = (event, modal) => {\n const link = event.target;\n currentFlavor = link.dataset.flavor;\n currentFlavorId = link.dataset.id;\n currentCategoryId = link.dataset.categoryid;\n currentCategoryName = link.dataset.categoryname;\n lastFlavor[currentCategoryId] = currentFlavorId;\n\n // Remove active from all and set to selected.\n const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item');\n links.forEach(node => node.classList.remove('active'));\n link.classList.add('active');\n const category = modal.getRoot()[0].querySelector('.nav-link[data-categoryid=\"' + currentCategoryId + '\"]');\n category.classList.add('active');\n\n const componentButtons = modal.getRoot()[0].querySelectorAll('.elements-buttons-preview button');\n componentButtons.forEach(componentButton => {\n // Remove previous flavor.\n if (componentButton.dataset.flavor != undefined) {\n componentButton.classList.remove(componentButton.dataset.flavor);\n }\n componentButton.classList.add(currentFlavor);\n componentButton.dataset.flavor = currentFlavor;\n if (\n (componentButton.dataset.flavorlist == '' || componentButton.dataset.flavorlist.split(',').includes(currentFlavor)) &&\n componentButton.dataset.category == currentCategoryName\n ) {\n componentButton.classList.remove('elements-hidden');\n if (componentButton.dataset.flavorlist != '') {\n let variants = getVariantsClass(data.getComponentById(componentButton.dataset.id).name, currentFlavor);\n let availableVariants = componentButton.querySelectorAll('.elements-button-variant');\n availableVariants.forEach((variant) => {\n updateVariantButtonState(variant, variants.indexOf(variant.dataset.variantclass) != -1);\n });\n }\n } else {\n componentButton.classList.add('elements-hidden');\n }\n });\n\n};\n\n/**\n * When opening the category dropdown, try to load remembered flavor.\n *\n * @param {MouseEvent} event The change event\n * @param {obj} modal\n */\nconst handleCategoryRemember = (event, modal) => {\n const link = event.target;\n currentCategoryId = link.dataset.categoryid;\n currentCategoryName = link.dataset.categoryname;\n currentFlavorId = lastFlavor[currentCategoryId];\n\n if (currentFlavorId != undefined) {\n // Call handleCategoryFlavorClick with tampered data.\n let e = {target: modal.getRoot()[0].querySelector('.elements-category-flavor[data-id=\"' + currentFlavorId + '\"]')};\n handleCategoryFlavorClick(e, modal);\n }\n};\n\n/**\n * Handle when closing the Modal.\n *\n * @param {obj} editor\n */\nconst handleModalHidden = (editor) => {\n editor.targetElm.closest('body').classList.remove('elements-modal-no-preview');\n if (currentCategoryId != 0 && currentFlavorId != 0) {\n savePreferences([\n {type: Preferences.category, value: currentCategoryId},\n {type: Preferences.category_flavors, value: JSON.stringify(lastFlavor)},\n {type: Preferences.component_variants, value: JSON.stringify(getVariantPreferences())}\n ]);\n }\n};\n\nconst updateComponentCode = (componentCode, selectedButton, placeholder, flavor = '') => {\n componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder);\n const comp = data.getComponentById(selectedButton);\n // Return active variants for current component.\n const variants = getVariantsClass(comp.name, flavor);\n\n // Apply variants to html component.\n if (variants.length > 0) {\n componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' '));\n componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(comp.name, flavor));\n } else {\n componentCode = componentCode.replace('{{VARIANTS}}', '');\n componentCode = componentCode.replace('{{VARIANTSHTML}}', '');\n }\n\n if (currentFlavor) {\n componentCode = componentCode.replace('{{FLAVOR}}', 'elements-' + currentFlavor + '-flavor');\n } else {\n componentCode = componentCode.replace('{{FLAVOR}}', '');\n }\n\n componentCode = componentCode.replace('{{COMPONENT}}', 'elements-' + comp.name);\n componentCode = componentCode.replace('{{CATEGORY}}', 'elements-' + data.getCategoryById(currentCategoryId).name);\n\n // Apply random IDs.\n componentCode = applyRandomID(componentCode);\n\n // Apply lang strings.\n componentCode = applyLangStrings(componentCode);\n\n return componentCode;\n};\n\n/**\n * Handle a click in a component button.\n *\n * @param {MouseEvent} event The click event\n * @param {obj} editor\n * @param {obj} modal\n */\nconst handleButtonClick = async(event, editor, modal) => {\n const selectedButton = event.target.closest('button').dataset.id;\n\n const comp = data.getComponentById(selectedButton);\n\n // Component button.\n if (comp) {\n let componentCode = comp.code;\n const placeholder = (selection.length > 0 ? selection : comp.text);\n\n let flavor = comp.flavors.length > 0 ? currentFlavor : '';\n\n // Create a new node to replace the placeholder.\n const randomId = generateRandomID();\n const newNode = document.createElement('span');\n newNode.dataset.id = randomId;\n newNode.innerHTML = placeholder;\n componentCode = updateComponentCode(componentCode, selectedButton, newNode.outerHTML, flavor);\n // Sets new content.\n editor.selection.setContent(componentCode);\n\n // Select text.\n const nodeSel = editor.dom.select('span[data-id=\"' + randomId + '\"]');\n if (nodeSel?.[0]) {\n editor.selection.select(nodeSel[0]);\n }\n\n modal.destroy();\n editor.focus();\n }\n};\n\n/**\n * Handle a mouse events mouseenter/mouseleave in a component button.\n *\n * @param {MouseEvent} event The click event\n * @param {obj} modal\n * @param {bool} show\n */\nconst handleButtonMouseEvent = (event, modal, show) => {\n const selectedButton = event.target.closest('button').dataset.id;\n const node = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-' + selectedButton + '\"]');\n const previewDefault = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-default\"]');\n const comp = data.getComponentById(selectedButton);\n\n let flavor = comp.flavors.length > 0 ? currentFlavor : '';\n\n const placeholder = (selection.length > 0 ? selection : comp.text);\n\n node.innerHTML = updateComponentCode(comp.code, selectedButton, placeholder, flavor);\n\n if (node) {\n if (show) {\n previewDefault.classList.toggle('elements-hidden');\n node.classList.toggle('elements-hidden');\n } else {\n node.classList.toggle('elements-hidden');\n previewDefault.classList.toggle('elements-hidden');\n }\n }\n};\n\n/**\n * Handle a mouse event within the variant buttons.\n *\n * @param {MouseEvent} event The mouseenter/mouseleave event\n * @param {obj} modal\n */\nconst handleVariantClick = (event, modal) => {\n event.stopPropagation();\n const variant = event.target.closest('span');\n const button = event.target.closest('button');\n const comp = data.getComponentById(button.dataset.id);\n const flavor = comp.flavors.length > 0 ? currentFlavor : '';\n\n updateVariantComponentState(variant, button, modal, false, true);\n\n const node = modal.getRoot()[0].querySelector('div[data-id=\"code-preview-' + button.dataset.id + '\"]');\n node.innerHTML = updateComponentCode(\n comp.code,\n button.dataset.id,\n comp.text,\n flavor\n );\n};\n\n/**\n * Update a variant component UI.\n *\n * @param {obj} variant\n * @param {obj} button\n * @param {obj} modal\n * @param {bool} show\n * @param {bool} updateHtml\n */\nconst updateVariantComponentState = (variant, button, modal, show, updateHtml) => {\n const selectedVariant = variant.dataset.variantclass;\n const selectedButton = button.dataset.id;\n const componentClass = button.dataset.classcomponent;\n const previewComponent = modal.getRoot()[0]\n .querySelector('div[data-id=\"code-preview-' + button.dataset.id + '\"] .' + componentClass);\n const variantPreview = modal.getRoot()[0]\n .querySelector('span[data-id=\"variantHTML-' + button.dataset.id + '\"]');\n const comp = data.getComponentById(selectedButton);\n let variantsHtml = '';\n let hasflavors = comp.flavors.length > 0;\n\n if (previewComponent) {\n if (updateHtml) {\n if (variant.dataset.state == 'on') {\n removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, false);\n previewComponent.classList.remove(selectedVariant);\n } else {\n addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, true);\n previewComponent.classList.add(selectedVariant);\n }\n\n // Update variant preview HTML.\n if (variantPreview) {\n variantPreview.innerHTML = getVariantsHtml(comp.name, currentFlavor);\n }\n } else {\n variantsHtml = getVariantsHtml(comp.name, currentFlavor);\n if (show) {\n previewComponent.classList.add(selectedVariant);\n variantsHtml += getVariantHtml(variant.dataset.variant);\n } else {\n previewComponent.classList.remove(selectedVariant);\n }\n\n // Update variant preview HTML.\n if (variantPreview) {\n variantPreview.innerHTML = variantsHtml;\n }\n }\n } else {\n // Update variants preferences.\n if (variant.dataset.state == 'on') {\n removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, false);\n } else {\n addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : '');\n updateVariantButtonState(variant, true);\n }\n }\n};\n\n/**\n * Update a variant button UI.\n *\n * @param {obj} variant\n * @param {bool} activate\n */\nconst updateVariantButtonState = (variant, activate) => {\n if (activate) {\n variant.dataset.state = 'on';\n variant.classList.remove(variant.dataset.variant + '-variant-off');\n variant.classList.add(variant.dataset.variant + '-variant-on');\n variant.classList.add('on');\n } else {\n variant.dataset.state = 'off';\n variant.classList.remove(variant.dataset.variant + '-variant-on');\n variant.classList.add(variant.dataset.variant + '-variant-off');\n variant.classList.remove('on');\n }\n};\n\n/**\n * Show/hide buttons depend on selected context.\n *\n * @param {object} modal\n * @param {String} context\n */\nconst showCategoryButtons = (modal, context) => {\n const showNodes = modal.getRoot()[0].querySelectorAll('button[data-type=\"' + context + '\"]');\n const hideNodes = modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type=\"' + context + '\"])');\n\n showNodes.forEach(node => node.classList.remove('elements-hidden'));\n hideNodes.forEach(node => node.classList.add('elements-hidden'));\n};\n\n/**\n * Replace all localized strings.\n *\n * @param {String} text\n * @return {String} String with lang tags replaced with a localized string.\n */\nconst applyLangStrings = (text) => {\n const compRegex = /{{#([^}]*)}}/g;\n\n [...text.matchAll(compRegex)].forEach(strLang => {\n text = text.replace('{{#' + strLang[1] + '}}', data.getLangString(strLang[1]));\n });\n\n return text;\n};\n\n/**\n * Generates a random string.\n * @return {string} A random string\n */\nconst generateRandomID = () => {\n const timestamp = new Date().getTime();\n return 'R' + Math.round(Math.random() * 100000) + '-' + timestamp;\n};\n\n/**\n * Replace all ID tags with a random string.\n * @param {String} text\n * @return {String} String with all ID tags replaced with a random string.\n */\nconst applyRandomID = (text) => {\n const compRegex = /{{@ID}}/g;\n\n if (text.match(compRegex)) {\n text = text.replace(compRegex, generateRandomID());\n }\n\n return text;\n};\n"],"names":["currentFlavor","currentFlavorId","currentCategoryId","currentCategoryName","lastFlavor","selection","data","async","editor","getContent","Data","loadData","Preferences","category","category_flavors","componentVariants","component_variants","displayDialogue","templateContext","getTemplateContext","modal","ElementsModal","create","type","TYPE","large","modalClass","getPreviewElements","targetElm","closest","classList","add","show","getRoot","on","ModalEvents","hidden","handleModalHidden","soleCategories","querySelectorAll","forEach","node","addEventListener","event","handleCategoryClick","selectCategories","handleCategoryFlavorClick","handleCategoryRemember","handleButtonClick","handleButtonMouseEvent","handleVariantClick","length","savedCategory","savedFlavor","displayorder","click","dataset","categoryid","flavorlink","querySelector","link","target","categoryname","remove","showCategoryButtons","flavor","id","componentButton","undefined","flavorlist","split","includes","variants","getComponentById","name","variant","updateVariantButtonState","indexOf","variantclass","e","value","JSON","stringify","updateComponentCode","componentCode","selectedButton","placeholder","replace","comp","join","getCategoryById","applyRandomID","applyLangStrings","code","text","flavors","randomId","generateRandomID","newNode","document","createElement","innerHTML","outerHTML","setContent","nodeSel","dom","select","destroy","focus","previewDefault","toggle","stopPropagation","button","updateVariantComponentState","updateHtml","selectedVariant","componentClass","classcomponent","previewComponent","variantPreview","variantsHtml","hasflavors","state","activate","context","showNodes","hideNodes","matchAll","strLang","getLangString","timestamp","Date","getTime","Math","round","random","compRegex","match"],"mappings":";;;;;;;sLAgDIA,cAAgB,GAChBC,gBAAkB,EAClBC,kBAAoB,EACpBC,oBAAsB,GACtBC,WAAa,GACbC,UAAY,GACZC,KAAO,yBAOiBC,MAAAA,SACxBF,UAAYG,OAAOH,UAAUI,aAC7BH,KAAO,IAAII,eACP,0BAAaF,SACb,sBAAUA,SACV,wBAAYA,SACZ,sBAAUA,eAERF,KAAKK,oCACKL,MAEhBJ,mBAAoB,kCAAgBU,2BAAYC,UAChDT,YAAa,kCAAgBQ,2BAAYE,kBACtB,OAAfV,aACAA,WAAa,QAEbW,mBAAoB,kCAAgBH,2BAAYI,oBAC1B,OAAtBD,oBACAA,kBAAoB,4CAEDA,yBACjBE,gBAAgBT,eAQpBS,gBAAkBV,MAAAA,eACdW,gBAAkBZ,KAAKa,mBAAmBX,QAE1CY,YAAcC,qBAAcC,OAAO,CACrCC,KAAMF,qBAAcG,KACpBN,gBAAiBA,gBACjBO,OAAO,IAILC,WAAapB,KAAKqB,qBAAuB,iBAAmB,4BAGlEnB,OAAOoB,UAAUC,QAAQ,QAAQC,UAAUC,IAAIL,YAE/CN,MAAMY,OAGNZ,MAAMa,UAAUC,GAAGC,sBAAYC,QAAQ,KACnCC,kBAAkB7B,iBAIhB8B,eAAiBlB,MAAMa,UAAU,GAAGM,iBAAiB,iCAC3DD,eAAeE,SAAQC,OACnBA,KAAKC,iBAAiB,SAAUC,QAC5BC,oBAAoBD,MAAOvB,mBAK7ByB,iBAAmBzB,MAAMa,UAAU,GAAGM,iBAAiB,6BAC7DM,iBAAiBL,SAAQC,OACrBA,KAAKC,iBAAiB,SAAUC,QAC5BG,0BAA0BH,MAAOvB,aAKRA,MAAMa,UAAU,GAAGM,iBAAiB,6BAC5CC,SAAQC,OAC7BA,KAAKC,iBAAiB,SAAUC,QAC5BI,uBAAuBJ,MAAOvB,aAKtBA,MAAMa,UAAU,GAAGM,iBAAiB,4BAC5CC,SAAQC,OACZA,KAAKC,iBAAiB,SAAUC,QAC5BK,kBAAkBL,MAAOnC,OAAQY,UAEjCd,KAAKqB,uBACLc,KAAKC,iBAAiB,cAAeC,QACjCM,uBAAuBN,MAAOvB,OAAO,MAEzCqB,KAAKC,iBAAiB,cAAeC,QACjCM,uBAAuBN,MAAOvB,OAAO,aAMhCA,MAAMa,UAAU,GAAGM,iBAAiB,4BAC5CC,SAAQC,OACbA,KAAKC,iBAAiB,SAAUC,QAC5BO,mBAAmBP,MAAOvB,aAK9BkB,eAAea,OAAS,GAAKN,iBAAiBM,OAAS,EAAG,OACpDC,cAAgBlD,kBAChBmD,YAAcjD,WAAWF,sBACF,GAAzBoC,eAAea,QAAeb,eAAe,GAAGgB,aAAeT,iBAAiB,GAAGS,aACnFT,iBAAiB,GAAGU,QAEpBjB,eAAe,GAAGiB,QAED,GAAjBH,gBACAd,eAAeE,SAASC,OAChBA,KAAKe,QAAQC,YAAcL,eAC3BX,KAAKc,WAGbV,iBAAiBL,SAASC,OAClBA,KAAKe,QAAQC,YAAcL,eAC3BX,KAAKc,WAGTF,aAAa,OACPK,WAAatC,MAAMa,UAAU,GAAG0B,cAClC,sCAAwCN,YAAc,MAEtDK,YACAA,WAAWH,WAazBX,oBAAsB,CAACD,MAAOvB,eAC1BwC,KAAOjB,MAAMkB,OACnB3D,kBAAoB0D,KAAKJ,QAAQC,WACjCtD,oBAAsByD,KAAKJ,QAAQM,aAGrB1C,MAAMa,UAAU,GAAGM,iBAAiB,6BAC5CC,SAAQC,MAAQA,KAAKX,UAAUiC,OAAO,YAC5CH,KAAK9B,UAAUC,IAAI,UAGnBiC,oBAAoB5C,MAAOjB,sBASzB2C,0BAA4B,CAACH,MAAOvB,eAChCwC,KAAOjB,MAAMkB,OACnB7D,cAAgB4D,KAAKJ,QAAQS,OAC7BhE,gBAAkB2D,KAAKJ,QAAQU,GAC/BhE,kBAAoB0D,KAAKJ,QAAQC,WACjCtD,oBAAsByD,KAAKJ,QAAQM,aACnC1D,WAAWF,mBAAqBD,gBAGlBmB,MAAMa,UAAU,GAAGM,iBAAiB,6BAC5CC,SAAQC,MAAQA,KAAKX,UAAUiC,OAAO,YAC5CH,KAAK9B,UAAUC,IAAI,UACFX,MAAMa,UAAU,GAAG0B,cAAc,8BAAgCzD,kBAAoB,MAC7F4B,UAAUC,IAAI,UAEEX,MAAMa,UAAU,GAAGM,iBAAiB,oCAC5CC,SAAQ2B,qBAEiBC,MAAlCD,gBAAgBX,QAAQS,QACxBE,gBAAgBrC,UAAUiC,OAAOI,gBAAgBX,QAAQS,QAE7DE,gBAAgBrC,UAAUC,IAAI/B,eAC9BmE,gBAAgBX,QAAQS,OAASjE,cAEU,IAAtCmE,gBAAgBX,QAAQa,aAAoBF,gBAAgBX,QAAQa,WAAWC,MAAM,KAAKC,SAASvE,gBACpGmE,gBAAgBX,QAAQ3C,UAAYV,oBAWpCgE,gBAAgBrC,UAAUC,IAAI,2BAT9BoC,gBAAgBrC,UAAUiC,OAAO,mBACS,IAAtCI,gBAAgBX,QAAQa,WAAkB,KACtCG,UAAW,iCAAiBlE,KAAKmE,iBAAiBN,gBAAgBX,QAAQU,IAAIQ,KAAM1E,eAChEmE,gBAAgB5B,iBAAiB,4BACvCC,SAASmC,UACvBC,yBAAyBD,SAA4D,GAAnDH,SAASK,QAAQF,QAAQnB,QAAQsB,uBAgBjF/B,uBAAyB,CAACJ,MAAOvB,eAC7BwC,KAAOjB,MAAMkB,UACnB3D,kBAAoB0D,KAAKJ,QAAQC,WACjCtD,oBAAsByD,KAAKJ,QAAQM,aACnC7D,gBAAkBG,WAAWF,mBAENkE,MAAnBnE,gBAA8B,KAE1B8E,EAAI,CAAClB,OAAQzC,MAAMa,UAAU,GAAG0B,cAAc,sCAAwC1D,gBAAkB,OAC5G6C,0BAA0BiC,EAAG3D,SAS/BiB,kBAAqB7B,SACvBA,OAAOoB,UAAUC,QAAQ,QAAQC,UAAUiC,OAAO,6BACzB,GAArB7D,mBAA6C,GAAnBD,oDACV,CACZ,CAACsB,KAAMX,2BAAYC,SAAUmE,MAAO9E,mBACpC,CAACqB,KAAMX,2BAAYE,iBAAkBkE,MAAOC,KAAKC,UAAU9E,aAC3D,CAACmB,KAAMX,2BAAYI,mBAAoBgE,MAAOC,KAAKC,WAAU,6CAKnEC,oBAAsB,SAACC,cAAeC,eAAgBC,iBAAarB,8DAAS,GAC9EmB,cAAgBA,cAAcG,QAAQ,kBAAmBD,mBACnDE,KAAOlF,KAAKmE,iBAAiBY,gBAE7Bb,UAAW,iCAAiBgB,KAAKd,KAAMT,eAKzCmB,cAFAZ,SAASrB,OAAS,GAClBiC,cAAgBA,cAAcG,QAAQ,eAAgBf,SAASiB,KAAK,OACtCF,QAAQ,oBAAoB,gCAAgBC,KAAKd,KAAMT,UAErFmB,cAAgBA,cAAcG,QAAQ,eAAgB,KACxBA,QAAQ,mBAAoB,IAU9DH,eADAA,eALIA,cADApF,cACgBoF,cAAcG,QAAQ,aAAc,YAAcvF,cAAgB,WAElEoF,cAAcG,QAAQ,aAAc,KAG1BA,QAAQ,gBAAiB,YAAcC,KAAKd,OAC5Ca,QAAQ,eAAgB,YAAcjF,KAAKoF,gBAAgBxF,mBAAmBwE,MAG5GU,cAAgBO,cAAcP,eAG9BA,cAAgBQ,iBAAiBR,gBAY/BpC,kBAAoBzC,MAAMoC,MAAOnC,OAAQY,eACrCiE,eAAiB1C,MAAMkB,OAAOhC,QAAQ,UAAU2B,QAAQU,GAExDsB,KAAOlF,KAAKmE,iBAAiBY,mBAG/BG,KAAM,KACFJ,cAAgBI,KAAKK,WACnBP,YAAejF,UAAU8C,OAAS,EAAI9C,UAAYmF,KAAKM,SAEzD7B,OAASuB,KAAKO,QAAQ5C,OAAS,EAAInD,cAAgB,SAGjDgG,SAAWC,mBACXC,QAAUC,SAASC,cAAc,QACvCF,QAAQ1C,QAAQU,GAAK8B,SACrBE,QAAQG,UAAYf,YACpBF,cAAgBD,oBAAoBC,cAAeC,eAAgBa,QAAQI,UAAWrC,QAEtFzD,OAAOH,UAAUkG,WAAWnB,qBAGtBoB,QAAUhG,OAAOiG,IAAIC,OAAO,iBAAmBV,SAAW,MAC5DQ,MAAAA,SAAAA,QAAU,IACVhG,OAAOH,UAAUqG,OAAOF,QAAQ,IAGpCpF,MAAMuF,UACNnG,OAAOoG,UAWT3D,uBAAyB,CAACN,MAAOvB,MAAOY,cACpCqD,eAAiB1C,MAAMkB,OAAOhC,QAAQ,UAAU2B,QAAQU,GACxDzB,KAAOrB,MAAMa,UAAU,GAAG0B,cAAc,6BAA+B0B,eAAiB,MACxFwB,eAAiBzF,MAAMa,UAAU,GAAG0B,cAAc,uCAClD6B,KAAOlF,KAAKmE,iBAAiBY,oBAE/BpB,OAASuB,KAAKO,QAAQ5C,OAAS,EAAInD,cAAgB,SAEjDsF,YAAejF,UAAU8C,OAAS,EAAI9C,UAAYmF,KAAKM,KAE7DrD,KAAK4D,UAAYlB,oBAAoBK,KAAKK,KAAMR,eAAgBC,YAAarB,QAEzExB,OACIT,MACA6E,eAAe/E,UAAUgF,OAAO,mBAChCrE,KAAKX,UAAUgF,OAAO,qBAEtBrE,KAAKX,UAAUgF,OAAO,mBACtBD,eAAe/E,UAAUgF,OAAO,sBAWtC5D,mBAAqB,CAACP,MAAOvB,SAC/BuB,MAAMoE,wBACApC,QAAUhC,MAAMkB,OAAOhC,QAAQ,QAC/BmF,OAASrE,MAAMkB,OAAOhC,QAAQ,UAC9B2D,KAAOlF,KAAKmE,iBAAiBuC,OAAOxD,QAAQU,IAC5CD,OAASuB,KAAKO,QAAQ5C,OAAS,EAAInD,cAAgB,GAEzDiH,4BAA4BtC,QAASqC,OAAQ5F,OAAO,GAAO,GAE9CA,MAAMa,UAAU,GAAG0B,cAAc,6BAA+BqD,OAAOxD,QAAQU,GAAK,MAC5FmC,UAAYlB,oBACbK,KAAKK,KACLmB,OAAOxD,QAAQU,GACfsB,KAAKM,KACL7B,SAaFgD,4BAA8B,CAACtC,QAASqC,OAAQ5F,MAAOY,KAAMkF,oBACzDC,gBAAkBxC,QAAQnB,QAAQsB,aAClCO,eAAiB2B,OAAOxD,QAAQU,GAChCkD,eAAiBJ,OAAOxD,QAAQ6D,eAChCC,iBAAmBlG,MAAMa,UAAU,GACpC0B,cAAc,6BAA+BqD,OAAOxD,QAAQU,GAAK,OAASkD,gBACzEG,eAAiBnG,MAAMa,UAAU,GAClC0B,cAAc,6BAA+BqD,OAAOxD,QAAQU,GAAK,MAChEsB,KAAOlF,KAAKmE,iBAAiBY,oBAC/BmC,aAAe,GACfC,WAAajC,KAAKO,QAAQ5C,OAAS,EAEnCmE,iBACIJ,YAC6B,MAAzBvC,QAAQnB,QAAQkE,sCACFlC,KAAKd,KAAMC,QAAQnB,QAAQmB,QAAS8C,WAAazH,cAAgB,IAC/E4E,yBAAyBD,SAAS,GAClC2C,iBAAiBxF,UAAUiC,OAAOoD,+CAEvB3B,KAAKd,KAAMC,QAAQnB,QAAQmB,QAAS8C,WAAazH,cAAgB,IAC5E4E,yBAAyBD,SAAS,GAClC2C,iBAAiBxF,UAAUC,IAAIoF,kBAI/BI,iBACAA,eAAelB,WAAY,gCAAgBb,KAAKd,KAAM1E,kBAG1DwH,cAAe,gCAAgBhC,KAAKd,KAAM1E,eACtCgC,MACAsF,iBAAiBxF,UAAUC,IAAIoF,iBAC/BK,eAAgB,+BAAe7C,QAAQnB,QAAQmB,UAE/C2C,iBAAiBxF,UAAUiC,OAAOoD,iBAIlCI,iBACAA,eAAelB,UAAYmB,eAKN,MAAzB7C,QAAQnB,QAAQkE,sCACFlC,KAAKd,KAAMC,QAAQnB,QAAQmB,QAAS8C,WAAazH,cAAgB,IAC/E4E,yBAAyBD,SAAS,iCAEvBa,KAAKd,KAAMC,QAAQnB,QAAQmB,QAAS8C,WAAazH,cAAgB,IAC5E4E,yBAAyBD,SAAS,KAWxCC,yBAA2B,CAACD,QAASgD,YACnCA,UACAhD,QAAQnB,QAAQkE,MAAQ,KACxB/C,QAAQ7C,UAAUiC,OAAOY,QAAQnB,QAAQmB,QAAU,gBACnDA,QAAQ7C,UAAUC,IAAI4C,QAAQnB,QAAQmB,QAAU,eAChDA,QAAQ7C,UAAUC,IAAI,QAEtB4C,QAAQnB,QAAQkE,MAAQ,MACxB/C,QAAQ7C,UAAUiC,OAAOY,QAAQnB,QAAQmB,QAAU,eACnDA,QAAQ7C,UAAUC,IAAI4C,QAAQnB,QAAQmB,QAAU,gBAChDA,QAAQ7C,UAAUiC,OAAO,QAU3BC,oBAAsB,CAAC5C,MAAOwG,iBAC1BC,UAAYzG,MAAMa,UAAU,GAAGM,iBAAiB,qBAAuBqF,QAAU,MACjFE,UAAY1G,MAAMa,UAAU,GAAGM,iBAAiB,qCAAuCqF,QAAU,OAEvGC,UAAUrF,SAAQC,MAAQA,KAAKX,UAAUiC,OAAO,qBAChD+D,UAAUtF,SAAQC,MAAQA,KAAKX,UAAUC,IAAI,sBAS3C6D,iBAAoBE,WAGlBA,KAAKiC,SAFS,kBAEYvF,SAAQwF,UAClClC,KAAOA,KAAKP,QAAQ,MAAQyC,QAAQ,GAAK,KAAM1H,KAAK2H,cAAcD,QAAQ,QAGvElC,MAOLG,iBAAmB,WACfiC,WAAY,IAAIC,MAAOC,gBACtB,IAAMC,KAAKC,MAAsB,IAAhBD,KAAKE,UAAqB,IAAML,WAQtDvC,cAAiBG,aACb0C,UAAY,kBAEd1C,KAAK2C,MAAMD,aACX1C,KAAOA,KAAKP,QAAQiD,UAAWvC,qBAG5BH"} \ No newline at end of file diff --git a/amd/build/variants.min.js b/amd/build/variants.min.js deleted file mode 100644 index d84cb5a..0000000 --- a/amd/build/variants.min.js +++ /dev/null @@ -1,11 +0,0 @@ -define("tiny_c4l/variants",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0; -/** - * Tiny C4L components variants. - * - * @module tiny_c4l/variants - * @copyright 2023 Marc Català - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -const variants=[{id:0,name:"align-center",html:""},{id:1,name:"align-left",html:""},{id:2,name:"align-right",html:""},{id:3,name:"caption",html:'
Consectetur adipiscing elit.\n Marcus Tullius Cicero,\n De Finibus Bonorum et Malorum'}];_exports.default={variants:variants};return _exports.default})); - -//# sourceMappingURL=variants.min.js.map \ No newline at end of file diff --git a/amd/build/variants.min.js.map b/amd/build/variants.min.js.map deleted file mode 100644 index 7cc0b59..0000000 --- a/amd/build/variants.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"variants.min.js","sources":["../src/variants.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Tiny C4L components variants.\n *\n * @module tiny_c4l/variants\n * @copyright 2023 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst variants = [\n {\n id: 0,\n name: \"align-center\",\n html: \"\",\n },\n {\n id: 1,\n name: \"align-left\",\n html: \"\",\n },\n {\n id: 2,\n name: \"align-right\",\n html: \"\",\n },\n {\n id: 3,\n name: \"caption\",\n html:\n `
Consectetur adipiscing elit.\n \n Source: Phasellus a posuere nibh.
`,\n },\n {\n id: 4,\n name: \"comfort-reading\",\n html: \"\",\n },\n {\n id: 5,\n name: \"dont-card-only\",\n html: \"\",\n },\n {\n id: 6,\n name: \"full-width\",\n html: \"\",\n },\n {\n id: 7,\n name: \"ordered-list\",\n html: \"\",\n },\n {\n id: 8,\n name: \"quote\",\n html:\n `
Marcus Tullius Cicero,\n De Finibus Bonorum et Malorum
`,\n },\n];\n\nexport default {\n variants,\n};\n"],"names":["variants","id","name","html","_exports","default"],"mappings":";;;;;;;;AAuBA,MAAMA,SAAW,CACb,CACIC,GAAI,EACJC,KAAM,eACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,aACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,cACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,UACNC,KACK,2OAIT,CACIF,GAAI,EACJC,KAAM,kBACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,iBACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,aACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,eACNC,KAAM,IAEV,CACIF,GAAI,EACJC,KAAM,QACNC,KACK,sJAGXC,SAAAC,QAEa,CACPL,mBACP,OAAAI,SAAAC,OAAA"} \ No newline at end of file diff --git a/amd/build/variantslib.min.js b/amd/build/variantslib.min.js index 32d5505..54c1bc4 100644 --- a/amd/build/variantslib.min.js +++ b/amd/build/variantslib.min.js @@ -1,10 +1,11 @@ -define("tiny_c4l/variantslib",["exports","core/ajax","core/notification","./variants"],(function(_exports,_ajax,_notification,_variants){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("tiny_elements/variantslib",["exports","tiny_elements/helper"],(function(_exports,_helper){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.variantExists=_exports.setData=_exports.removeVariant=_exports.loadVariantPreferences=_exports.getVariantsHtml=_exports.getVariantsClass=_exports.getVariantPreferences=_exports.getVariantPreference=_exports.getVariantHtml=_exports.addVariant=void 0; /** - * Variants helper for C4L plugin. + * Variants helper for Elements plugin. * - * @module tiny_c4l/variantslib + * @module tiny_elements/variantslib * @copyright 2023 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.variantExists=_exports.saveVariantPreferences=_exports.removeVariant=_exports.loadVariantPreferences=_exports.getVariantsHtml=_exports.getVariantsClass=_exports.getVariantHtml=_exports.addVariant=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);let variantPreferences={};_exports.loadVariantPreferences=async components=>{const request={methodname:"core_user_get_user_preferences",args:{name:"c4l_components_variants"}};return _ajax.default.call([request])[0].then((result=>{let comp={},rawPreferences={},variantComp={},variantObj={};try{rawPreferences=JSON.parse(result.preferences[0].value)}catch(e){_notification.default.exception(e)}null!==rawPreferences&&Object.keys(rawPreferences).forEach((preference=>{comp=components.find((component=>component.id==preference)),null!=comp&&(variantPreferences[comp.name]=[],rawPreferences[preference].forEach((variant=>{variantObj=_variants.variants.find((element=>element.id==variant)),null!=variantObj&&(variantComp=comp.variants.find((element=>element==variantObj.name)),null!=variantComp&&variantPreferences[comp.name].push(variantObj.name))})))}))})).catch(_notification.default.exception)};_exports.saveVariantPreferences=components=>{let comp={},rawPreferences={},variantObj={};Object.keys(variantPreferences).forEach((preference=>{comp=components.find((component=>component.name==preference)),null!=comp&&(rawPreferences[comp.id]=[],variantPreferences[preference].forEach((variant=>{variantObj=_variants.variants.find((element=>element.name==variant)),null!=variantObj&&rawPreferences[comp.id].push(variantObj.id)})))}));const request={methodname:"core_user_update_user_preferences",args:{preferences:[{type:"c4l_components_variants",value:JSON.stringify(rawPreferences)}]}};return _ajax.default.call([request])[0].catch(_notification.default.exception)};const variantExists=(component,variant)=>variantPreferences?.[component]&&-1!==variantPreferences[component].indexOf(variant);_exports.variantExists=variantExists;_exports.getVariantsClass=component=>{let variants=[];return variantPreferences?.[component]&&variantPreferences[component].forEach((variant=>{variants.push("c4l-"+variant+"-variant")})),variants};_exports.getVariantsHtml=component=>{let variantsHtml="",variantObj={};return variantPreferences?.[component]&&variantPreferences[component].forEach((variant=>{variantObj=_variants.variants.find((element=>element.name==variant)),null!=variantObj&&(variantsHtml+=variantObj.html)})),variantsHtml};_exports.getVariantHtml=variant=>{let variantHtml=[],variantObj={};return variantObj=_variants.variants.find((element=>element.name==variant)),null!=variantObj&&(variantHtml=variantObj.html),variantHtml};_exports.addVariant=(component,variant)=>{variantPreferences?.[component]||(variantPreferences[component]=[]),variantExists(component,variant)||variantPreferences[component].push(variant)};_exports.removeVariant=(component,variant)=>{const index=variantPreferences[component].indexOf(variant);-1!==index&&delete variantPreferences[component][index]}})); + */ +let variantPreferences={},data={};_exports.setData=values=>{data=values};_exports.getVariantPreferences=()=>variantPreferences;_exports.loadVariantPreferences=preferences=>{variantPreferences=null!=preferences?preferences:{}};const getVariantPreference=function(component){let flavor=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",componentObj=(0,_helper.findByName)(data.getComponents(),component),flavorObj=(0,_helper.findByName)(data.getFlavors(),flavor);return void 0===componentObj?[]:""!=flavor||variantPreferences[componentObj.id]?""!=flavor&&void 0===flavorObj?[]:""==flavor||variantPreferences[componentObj.id+"-"+flavorObj.id]?""==flavor?variantPreferences[componentObj.id]:variantPreferences[componentObj.id+"-"+flavorObj.id]:[]:[]};_exports.getVariantPreference=getVariantPreference;const variantExists=function(component,variant){let flavor=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",variantObj=(0,_helper.findByName)(data.getVariants(),variant);return-1!==getVariantPreference(component,flavor).indexOf(variantObj.id)};_exports.variantExists=variantExists;_exports.getVariantsClass=function(component){let flavor=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",variants=[];return getVariantPreference(component,flavor).forEach((variant=>{let variantObj=(0,_helper.findById)(data.getVariants(),variant);void 0!==variantObj&&variants.push((variantObj.c4lcompatibility?"c4l":"elements")+"-"+variantObj.name+"-variant")})),variants};_exports.getVariantsHtml=(component,flavor)=>{let variantsHtml="";return getVariantPreference(component,flavor).forEach((variant=>{let variantObj=(0,_helper.findById)(data.getVariants(),variant);void 0!==variantObj&&(variantsHtml+=variantObj.content)})),variantsHtml};_exports.getVariantHtml=variant=>{let variantHtml=[],variantObj={};return variantObj=(0,_helper.findByName)(data.getVariants(),variant),null!=variantObj&&(variantHtml=variantObj.html),variantHtml};_exports.addVariant=function(component,variant){let flavor=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",componentObj=(0,_helper.findByName)(data.getComponents(),component),variantObj=(0,_helper.findByName)(data.getVariants(),variant),flavorObj=(0,_helper.findByName)(data.getFlavors(),flavor);""==flavor?(variantPreferences[componentObj.id]||(variantPreferences[componentObj.id]=[]),variantExists(component,variant)||variantPreferences[componentObj.id].push(variantObj.id)):(variantPreferences[componentObj.id+"-"+flavorObj.id]||(variantPreferences[componentObj.id+"-"+flavorObj.id]=[]),variantExists(component,variant,flavor)||variantPreferences[componentObj.id+"-"+flavorObj.id].push(variantObj.id))};_exports.removeVariant=function(component,variant){let flavor=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",componentObj=(0,_helper.findByName)(data.getComponents(),component),variantObj=(0,_helper.findByName)(data.getVariants(),variant),flavorObj=(0,_helper.findByName)(data.getFlavors(),flavor);if(""!=flavor){let index=variantPreferences[componentObj.id+"-"+flavorObj.id].indexOf(variantObj.id);-1!==index&&delete variantPreferences[componentObj.id+"-"+flavorObj.id][index],0==variantPreferences[componentObj.id+"-"+flavorObj.id].length&&delete variantPreferences[componentObj.id+"-"+flavorObj.id]}else{let index=variantPreferences[componentObj.id].indexOf(variantObj.id);-1!==index&&delete variantPreferences[componentObj.id][index],0==variantPreferences[componentObj.id].length&&delete variantPreferences[componentObj.id]}}})); //# sourceMappingURL=variantslib.min.js.map \ No newline at end of file diff --git a/amd/build/variantslib.min.js.map b/amd/build/variantslib.min.js.map index 601c41e..bd2bdfe 100644 --- a/amd/build/variantslib.min.js.map +++ b/amd/build/variantslib.min.js.map @@ -1 +1 @@ -{"version":3,"file":"variantslib.min.js","sources":["../src/variantslib.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Variants helper for C4L plugin.\n *\n * @module tiny_c4l/variantslib\n * @copyright 2023 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport {variants as VARIANTS} from './variants';\n\nconst variantsPreferenceName = 'c4l_components_variants';\nlet variantPreferences = {};\n\n/**\n * Load user preferences.\n *\n * @param {object} components [description]\n * @returns {Promise}\n */\nexport const loadVariantPreferences = async(components) => {\n\n const request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n name: variantsPreferenceName\n }\n };\n\n return Ajax.call([request])[0]\n .then(result => {\n let comp = {};\n let rawPreferences = {};\n let variantComp = {};\n let variantObj = {};\n try {\n rawPreferences = JSON.parse(result.preferences[0].value);\n } catch (e) {\n Notification.exception(e);\n }\n\n if (rawPreferences !== null) {\n Object.keys(rawPreferences).forEach(preference => {\n comp = components.find(component => component.id == preference);\n if (comp != undefined) {\n variantPreferences[comp.name] = [];\n rawPreferences[preference].forEach((variant) => {\n variantObj = VARIANTS.find(element => element.id == variant);\n if (variantObj != undefined) {\n // Avoid invalid variants saved previously.\n variantComp = comp.variants.find(element => element == variantObj.name);\n if (variantComp != undefined) {\n variantPreferences[comp.name].push(variantObj.name);\n }\n }\n });\n }\n });\n }\n }).catch(Notification.exception);\n};\n\n/**\n * Save user preferences.\n *\n * @param {object} components [description]\n * @returns {Promise}\n */\nexport const saveVariantPreferences = (components) => {\n let comp = {};\n let rawPreferences = {};\n let variantObj = {};\n Object.keys(variantPreferences).forEach(preference => {\n comp = components.find(component => component.name == preference);\n if (comp != undefined) {\n rawPreferences[comp.id] = [];\n variantPreferences[preference].forEach((variant) => {\n variantObj = VARIANTS.find(element => element.name == variant);\n if (variantObj != undefined) {\n rawPreferences[comp.id].push(variantObj.id);\n }\n });\n }\n });\n\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [\n {\n type: variantsPreferenceName,\n value: JSON.stringify(rawPreferences)\n }\n ]\n }\n };\n\n return Ajax.call([request])[0].catch(Notification.exception);\n};\n\n\n/**\n * Returns whether a variant exists for a component.\n *\n * @param {string} component Component name\n * @param {string} variant Variant name\n * @returns {bool}\n */\nexport const variantExists = (component, variant) => {\n return variantPreferences?.[component] && variantPreferences[component].indexOf(variant) !== -1;\n};\n\n/**\n * Returns each variant for a component as a CSS class.\n *\n * @param {string} component Component name\n * @returns {Array}\n */\nexport const getVariantsClass = (component) => {\n let variants = [];\n\n if (variantPreferences?.[component]) {\n variantPreferences[component].forEach(variant => {\n variants.push('c4l-' + variant + '-variant');\n });\n }\n return variants;\n};\n\n/**\n * Return all HTML variants for a component.\n *\n * @param {string} component Component name\n * @returns {string}\n */\nexport const getVariantsHtml = (component) => {\n let variantsHtml = '';\n let variantObj = {};\n\n if (variantPreferences?.[component]) {\n variantPreferences[component].forEach(variant => {\n variantObj = VARIANTS.find(element => element.name == variant);\n if (variantObj != undefined) {\n variantsHtml += variantObj.html;\n }\n });\n }\n return variantsHtml;\n};\n\n/**\n * Return the HTML variant.\n *\n * @param {string} variant Variant name\n * @returns {string}\n */\nexport const getVariantHtml = (variant) => {\n let variantHtml = [];\n let variantObj = {};\n\n variantObj = VARIANTS.find(element => element.name == variant);\n if (variantObj != undefined) {\n variantHtml = variantObj.html;\n }\n return variantHtml;\n};\n\n/**\n * Add a variant to variantPreferences\n *\n * @param {string} component Component name\n * @param {string} variant Variant name\n */\nexport const addVariant = (component, variant) => {\n if (!variantPreferences?.[component]) {\n variantPreferences[component] = [];\n }\n if (!variantExists(component, variant)) {\n variantPreferences[component].push(variant);\n }\n};\n\n/**\n * Remove a variant from variantPreferences\n *\n * @param {string} component Component name\n * @param {string} variant Variant name\n */\nexport const removeVariant = (component, variant) => {\n const index = variantPreferences[component].indexOf(variant);\n if (index !== -1) {\n delete variantPreferences[component][index];\n }\n};\n"],"names":["_interopRequireDefault","obj","__esModule","default","_ajax","_notification","variantPreferences","_exports","loadVariantPreferences","async","request","methodname","args","name","Ajax","call","then","result","comp","rawPreferences","variantComp","variantObj","JSON","parse","preferences","value","e","Notification","exception","Object","keys","forEach","preference","components","find","component","id","undefined","variant","VARIANTS","element","variants","push","catch","saveVariantPreferences","type","stringify","variantExists","indexOf","getVariantsClass","getVariantsHtml","variantsHtml","html","getVariantHtml","variantHtml","addVariant","removeVariant","index"],"mappings":"yIAwB6C,SAAAA,uBAAAC,KAAAA,OAAAA,KAAAA,IAAAC,WAAAD,IAAAE,CAAAA,QAAAF,IAAA;;;;;;;iRAD7CG,MAAAJ,uBAAAI,OACAC,cAAAL,uBAAAK,eAIA,IAAIC,mBAAqB,CAAA,EAgDvBC,SAAAC,uBAxCoCC,mBAElC,MAAMC,QAAU,CACZC,WAAY,iCACZC,KAAM,CACFC,KAdmB,4BAkB3B,OAAOC,MAAIX,QAACY,KAAK,CAACL,UAAU,GACvBM,MAAKC,SACF,IAAIC,KAAO,CAAA,EACPC,eAAiB,CAAA,EACjBC,YAAc,CAAA,EACdC,WAAa,CAAA,EACjB,IACIF,eAAiBG,KAAKC,MAAMN,OAAOO,YAAY,GAAGC,MACrD,CAAC,MAAOC,GACLC,cAAAA,QAAaC,UAAUF,EAC3B,CAEuB,OAAnBP,gBACAU,OAAOC,KAAKX,gBAAgBY,SAAQC,aAChCd,KAAOe,WAAWC,MAAKC,WAAaA,UAAUC,IAAMJ,aACxCK,MAARnB,OACAZ,mBAAmBY,KAAKL,MAAQ,GAChCM,eAAea,YAAYD,SAASO,UAChCjB,WAAakB,UAAAA,SAASL,MAAKM,SAAWA,QAAQJ,IAAME,UAClCD,MAAdhB,aAEAD,YAAcF,KAAKuB,SAASP,MAAKM,SAAWA,SAAWnB,WAAWR,OAC/CwB,MAAfjB,aACAd,mBAAmBY,KAAKL,MAAM6B,KAAKrB,WAAWR,MAEtD,IAER,GAER,IACL8B,MAAMhB,cAAYxB,QAACyB,UAAU,EAuClCrB,SAAAqC,uBA9BqCX,aACnC,IAAIf,KAAO,CAAA,EACPC,eAAiB,CAAA,EACjBE,WAAa,CAAA,EACjBQ,OAAOC,KAAKxB,oBAAoByB,SAAQC,aACpCd,KAAOe,WAAWC,MAAKC,WAAaA,UAAUtB,MAAQmB,aAC1CK,MAARnB,OACAC,eAAeD,KAAKkB,IAAM,GAC1B9B,mBAAmB0B,YAAYD,SAASO,UACpCjB,WAAakB,UAAAA,SAASL,MAAKM,SAAWA,QAAQ3B,MAAQyB,UACpCD,MAAdhB,YACAF,eAAeD,KAAKkB,IAAIM,KAAKrB,WAAWe,GAC5C,IAER,IAGJ,MAAM1B,QAAU,CACZC,WAAY,oCACZC,KAAM,CACFY,YAAa,CACT,CACIqB,KA/EW,0BAgFXpB,MAAOH,KAAKwB,UAAU3B,oBAMtC,OAAOL,cAAKC,KAAK,CAACL,UAAU,GAAGiC,MAAMhB,cAAYxB,QAACyB,UAAU,EAWzD,MAAMmB,cAAgBA,CAACZ,UAAWG,UAC9BhC,qBAAqB6B,aAAkE,IAApD7B,mBAAmB6B,WAAWa,QAAQV,SAClF/B,SAAAwC,cAAAA,cAiBAxC,SAAA0C,iBAT+Bd,YAC7B,IAAIM,SAAW,GAOf,OALInC,qBAAqB6B,YACrB7B,mBAAmB6B,WAAWJ,SAAQO,UAClCG,SAASC,KAAK,OAASJ,QAAU,WAAW,IAG7CG,QAAQ,EAsBjBlC,SAAA2C,gBAb8Bf,YAC5B,IAAIgB,aAAe,GACf9B,WAAa,CAAA,EAUjB,OARIf,qBAAqB6B,YACrB7B,mBAAmB6B,WAAWJ,SAAQO,UAClCjB,WAAakB,UAAAA,SAASL,MAAKM,SAAWA,QAAQ3B,MAAQyB,UACpCD,MAAdhB,aACA8B,cAAgB9B,WAAW+B,KAC/B,IAGDD,YAAY,EAkBrB5C,SAAA8C,eAT6Bf,UAC3B,IAAIgB,YAAc,GACdjC,WAAa,CAAA,EAMjB,OAJAA,WAAakB,UAAAA,SAASL,MAAKM,SAAWA,QAAQ3B,MAAQyB,UACpCD,MAAdhB,aACAiC,YAAcjC,WAAW+B,MAEtBE,WAAW,EAgBpB/C,SAAAgD,WAPwBA,CAACpB,UAAWG,WAC7BhC,qBAAqB6B,aACtB7B,mBAAmB6B,WAAa,IAE/BY,cAAcZ,UAAWG,UAC1BhC,mBAAmB6B,WAAWO,KAAKJ,QACvC,EAcF/B,SAAAiD,cAL2BA,CAACrB,UAAWG,WACrC,MAAMmB,MAAQnD,mBAAmB6B,WAAWa,QAAQV,UACrC,IAAXmB,cACOnD,mBAAmB6B,WAAWsB,MACzC,CACF"} \ No newline at end of file +{"version":3,"file":"variantslib.min.js","sources":["../src/variantslib.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Variants helper for Elements plugin.\n *\n * @module tiny_elements/variantslib\n * @copyright 2023 Marc Català \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {\n findById,\n findByName\n} from 'tiny_elements/helper';\n\nlet variantPreferences = {};\nlet data = {};\n\n/**\n * Set the data object.\n * @param {object} values\n */\nexport const setData = (values) => {\n data = values;\n};\n\n/**\n * Get the variant preferences.\n * @returns {object}\n */\nexport const getVariantPreferences = () => {\n return variantPreferences;\n};\n\n/**\n * Load user preferences.\n * @param {array} preferences\n * @returns {Promise}\n */\nexport const loadVariantPreferences = (preferences) => {\n if (preferences !== undefined && preferences !== null) {\n variantPreferences = preferences;\n } else {\n variantPreferences = {};\n }\n};\n\n/**\n * Get variant preferences for a single component-flavor combination.\n * @param {*} component\n * @param {*} flavor\n * @returns\n */\nexport const getVariantPreference = (component, flavor = '') => {\n let componentObj = findByName(data.getComponents(), component);\n let flavorObj = findByName(data.getFlavors(), flavor);\n\n if (componentObj === undefined) {\n return [];\n }\n\n if (flavor == '' && !variantPreferences[componentObj.id]) {\n return [];\n }\n\n if (flavor != '' && flavorObj === undefined) {\n return [];\n }\n\n if (flavor != '' && !variantPreferences[componentObj.id + '-' + flavorObj.id]) {\n return [];\n }\n\n if (flavor == '') {\n return variantPreferences[componentObj.id];\n } else {\n return variantPreferences[componentObj.id + '-' + flavorObj.id];\n }\n};\n\n/**\n * Returns whether a variant exists for a component.\n *\n * @param {string} component Component name\n * @param {string} variant Variant name\n * @param {string} flavor Flavor name\n * @returns {bool}\n */\nexport const variantExists = (component, variant, flavor = '') => {\n let variantObj = findByName(data.getVariants(), variant);\n return getVariantPreference(component, flavor).indexOf(variantObj.id) !== -1;\n};\n\n/**\n * Returns each variant for a component as a CSS class.\n *\n * @param {string} component Component name\n * @param {string} flavor Flavor name\n * @returns {Array}\n */\nexport const getVariantsClass = (component, flavor = '') => {\n let variants = [];\n getVariantPreference(component, flavor).forEach(variant => {\n let variantObj = findById(data.getVariants(), variant);\n if (variantObj !== undefined) {\n variants.push((variantObj.c4lcompatibility ? 'c4l' : 'elements') + '-' + variantObj.name + '-variant');\n }\n });\n\n return variants;\n};\n\n/**\n * Return all HTML variants for a component.\n *\n * @param {string} component Component name\n * @param {string} flavor Flavor name\n * @returns {string}\n */\nexport const getVariantsHtml = (component, flavor) => {\n let variantsHtml = '';\n\n getVariantPreference(component, flavor).forEach(variant => {\n let variantObj = findById(data.getVariants(), variant);\n if (variantObj !== undefined) {\n variantsHtml += variantObj.content;\n }\n });\n\n return variantsHtml;\n};\n\n/**\n * Return the HTML variant.\n *\n * @param {string} variant Variant name\n * @returns {string}\n */\nexport const getVariantHtml = (variant) => {\n let variantHtml = [];\n let variantObj = {};\n\n variantObj = findByName(data.getVariants(), variant);\n if (variantObj != undefined) {\n variantHtml = variantObj.html;\n }\n return variantHtml;\n};\n\n/**\n * Add a variant to variantPreferences\n *\n * @param {string} component Component name\n * @param {string} variant Variant name\n * @param {string} flavor Flavor name\n */\nexport const addVariant = (component, variant, flavor = '') => {\n let componentObj = findByName(data.getComponents(), component);\n let variantObj = findByName(data.getVariants(), variant);\n let flavorObj = findByName(data.getFlavors(), flavor);\n\n if (flavor == '') {\n if (!variantPreferences[componentObj.id]) {\n variantPreferences[componentObj.id] = [];\n }\n if (!variantExists(component, variant)) {\n variantPreferences[componentObj.id].push(variantObj.id);\n }\n } else {\n if (!variantPreferences[componentObj.id + '-' + flavorObj.id]) {\n variantPreferences[componentObj.id + '-' + flavorObj.id] = [];\n }\n if (!variantExists(component, variant, flavor)) {\n variantPreferences[componentObj.id + '-' + flavorObj.id].push(variantObj.id);\n }\n }\n};\n\n/**\n * Remove a variant from variantPreferences\n *\n * @param {string} component Component name\n * @param {string} variant Variant name\n * @param {string} flavor Flavor name\n */\nexport const removeVariant = (component, variant, flavor = '') => {\n let componentObj = findByName(data.getComponents(), component);\n let variantObj = findByName(data.getVariants(), variant);\n let flavorObj = findByName(data.getFlavors(), flavor);\n\n if (flavor != '') {\n let index = variantPreferences[componentObj.id + '-' + flavorObj.id].indexOf(variantObj.id);\n if (index !== -1) {\n delete variantPreferences[componentObj.id + '-' + flavorObj.id][index];\n }\n if (variantPreferences[componentObj.id + '-' + flavorObj.id].length == 0) {\n delete variantPreferences[componentObj.id + '-' + flavorObj.id];\n }\n } else {\n let index = variantPreferences[componentObj.id].indexOf(variantObj.id);\n if (index !== -1) {\n delete variantPreferences[componentObj.id][index];\n }\n if (variantPreferences[componentObj.id].length == 0) {\n delete variantPreferences[componentObj.id];\n }\n }\n};\n"],"names":["variantPreferences","data","values","preferences","getVariantPreference","component","flavor","componentObj","getComponents","flavorObj","getFlavors","undefined","id","variantExists","variant","variantObj","getVariants","indexOf","variants","forEach","push","c4lcompatibility","name","variantsHtml","content","variantHtml","html","index","length"],"mappings":";;;;;;;;IA4BIA,mBAAqB,GACrBC,KAAO,oBAMaC,SACpBD,KAAOC,uCAO0B,IAC1BF,mDAQ4BG,cAE/BH,mBADAG,MAAAA,YACqBA,YAEA,UAUhBC,qBAAuB,SAACC,eAAWC,8DAAS,GACjDC,cAAe,sBAAWN,KAAKO,gBAAiBH,WAChDI,WAAY,sBAAWR,KAAKS,aAAcJ,oBAEzBK,IAAjBJ,aACO,GAGG,IAAVD,QAAiBN,mBAAmBO,aAAaK,IAIvC,IAAVN,aAA8BK,IAAdF,UACT,GAGG,IAAVH,QAAiBN,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IAI5D,IAAVN,OACON,mBAAmBO,aAAaK,IAEhCZ,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IANrD,GARA,6DA0BFC,cAAgB,SAACR,UAAWS,aAASR,8DAAS,GACnDS,YAAa,sBAAWd,KAAKe,cAAeF,gBAC2B,IAApEV,qBAAqBC,UAAWC,QAAQW,QAAQF,WAAWH,oEAUtC,SAACP,eAAWC,8DAAS,GAC7CY,SAAW,UACfd,qBAAqBC,UAAWC,QAAQa,SAAQL,cACxCC,YAAa,oBAASd,KAAKe,cAAeF,cAC3BH,IAAfI,YACAG,SAASE,MAAML,WAAWM,iBAAmB,MAAQ,YAAc,IAAMN,WAAWO,KAAO,eAI5FJ,mCAUoB,CAACb,UAAWC,cACnCiB,aAAe,UAEnBnB,qBAAqBC,UAAWC,QAAQa,SAAQL,cACxCC,YAAa,oBAASd,KAAKe,cAAeF,cAC3BH,IAAfI,aACAQ,cAAgBR,WAAWS,YAI5BD,sCASoBT,cACvBW,YAAc,GACdV,WAAa,UAEjBA,YAAa,sBAAWd,KAAKe,cAAeF,SAC1BH,MAAdI,aACAU,YAAcV,WAAWW,MAEtBD,iCAUe,SAACpB,UAAWS,aAASR,8DAAS,GAChDC,cAAe,sBAAWN,KAAKO,gBAAiBH,WAChDU,YAAa,sBAAWd,KAAKe,cAAeF,SAC5CL,WAAY,sBAAWR,KAAKS,aAAcJ,QAEhC,IAAVA,QACKN,mBAAmBO,aAAaK,MACjCZ,mBAAmBO,aAAaK,IAAM,IAErCC,cAAcR,UAAWS,UAC1Bd,mBAAmBO,aAAaK,IAAIQ,KAAKL,WAAWH,MAGnDZ,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,MACtDZ,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IAAM,IAE1DC,cAAcR,UAAWS,QAASR,SACnCN,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IAAIQ,KAAKL,WAAWH,6BAYxD,SAACP,UAAWS,aAASR,8DAAS,GACnDC,cAAe,sBAAWN,KAAKO,gBAAiBH,WAChDU,YAAa,sBAAWd,KAAKe,cAAeF,SAC5CL,WAAY,sBAAWR,KAAKS,aAAcJ,WAEhC,IAAVA,OAAc,KACVqB,MAAQ3B,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IAAIK,QAAQF,WAAWH,KACzE,IAAXe,cACO3B,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IAAIe,OAEG,GAAnE3B,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,IAAIgB,eAClD5B,mBAAmBO,aAAaK,GAAK,IAAMH,UAAUG,QAE7D,KACCe,MAAQ3B,mBAAmBO,aAAaK,IAAIK,QAAQF,WAAWH,KACpD,IAAXe,cACO3B,mBAAmBO,aAAaK,IAAIe,OAEG,GAA9C3B,mBAAmBO,aAAaK,IAAIgB,eAC7B5B,mBAAmBO,aAAaK"} \ No newline at end of file diff --git a/amd/src/category_form_helper.js b/amd/src/category_form_helper.js new file mode 100644 index 0000000..9578e98 --- /dev/null +++ b/amd/src/category_form_helper.js @@ -0,0 +1,78 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Helper for autocomplete form element to select only variants that belong to the + * selected category. + * + * @module tiny_elements/category_form_helper + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; +import Notification from 'core/notification'; + +export default { + /** + * Process the results for auto complete elements. + * + * @param {String} selector The selector of the auto complete element. + * @param {Array} results An array or results. + * @return {Array} New array of results. + */ + processResults: function(selector, results) { + var options = []; + results.forEach((data) => { + options.push({ + value: data.name, + label: data.displayname + }); + }); + return options; + }, + + /** + * Source of data for Ajax element. + * + * @param {String} selector The selector of the auto complete element. + * @param {String} query The query string. + * @param {Function} callback A callback function receiving an array of results. + */ + /* eslint-disable promise/no-callback-in-promise */ + transport: function(selector, query, callback) { + var el = document.querySelector(selector); + if (!el) { + return; + } + const contextid = el.dataset.contextid ?? 1; + const categoryname = el.closest('form').querySelector('select[name="categoryname"]').value; + + let methodname = 'tiny_elements_get_variants'; + if (el.name == 'flavors[]') { + methodname = 'tiny_elements_get_flavors'; + } + + Ajax.call([{ + methodname: methodname, + args: { + contextid: contextid, + categoryname: categoryname, + query: query, + } + }])[0].then(callback).catch(Notification.exception); + } +}; diff --git a/amd/src/commands.js b/amd/src/commands.js index cc7b6b3..a57ed61 100644 --- a/amd/src/commands.js +++ b/amd/src/commands.js @@ -14,60 +14,54 @@ // along with Moodle. If not, see . /** - * Tiny C4L commands. + * Tiny Elements commands. * - * @module tiny_c4l/commands + * @module tiny_elements/commands * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {getButtonImage} from 'editor_tiny/utils'; import {get_string as getString} from 'core/str'; -import {handleAction} from './ui'; +import {handleAction} from 'tiny_elements/ui'; import { component, - c4lButtonName, - c4lMenuItemName, + elementsButtonName, + elementsMenuItemName, icon, -} from './common'; -import { - isC4LVisible, - getpreviewCSS -} from './options'; +} from 'tiny_elements/common'; +import {isElementsVisible} from 'tiny_elements/options'; export const getSetup = async() => { const [ - c4lButtonNameTitle, - c4lMenuItemNameTitle, + elementsButtonNameTitle, + elementsMenuItemNameTitle, buttonImage, ] = await Promise.all([ - getString('button_c4l', component), - getString('menuitem_c4l', component), + getString('button_elements', component), + getString('menuitem_elements', component), getButtonImage('icon', component), ]); return (editor) => { - if (isC4LVisible(editor)) { - // Register the C4L Icon. + if (isElementsVisible(editor)) { + // Register the Elements Icon. editor.ui.registry.addIcon(icon, buttonImage.html); - // Register the C4L Toolbar Button. - editor.ui.registry.addButton(c4lButtonName, { + // Register the Elements Toolbar Button. + editor.ui.registry.addButton(elementsButtonName, { icon, - tooltip: c4lButtonNameTitle, + tooltip: elementsButtonNameTitle, onAction: () => handleAction(editor), }); - // Add the C4L Menu Item. + // Add the Elements Menu Item. // This allows it to be added to a standard menu, or a context menu. - editor.ui.registry.addMenuItem(c4lMenuItemName, { + editor.ui.registry.addMenuItem(elementsMenuItemName, { icon, - text: c4lMenuItemNameTitle, + text: elementsMenuItemNameTitle, onAction: () => handleAction(editor), }); - - // Inject custom CSS. - editor.options.set('content_style', getpreviewCSS(editor)); } }; }; diff --git a/amd/src/common.js b/amd/src/common.js index 2db0509..66a4e29 100644 --- a/amd/src/common.js +++ b/amd/src/common.js @@ -14,19 +14,19 @@ // along with Moodle. If not, see . /** - * Tiny C4L common. + * Tiny Elements common. * - * @module tiny_c4l/common + * @module tiny_elements/common * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const component = 'tiny_c4l'; +const component = 'tiny_elements'; export default { component, pluginName: `${component}/plugin`, icon: `${component}`, - c4lButtonName: `${component}`, - c4lMenuItemName: `${component}`, + elementsButtonName: `${component}`, + elementsMenuItemName: `${component}`, }; diff --git a/amd/src/components.js b/amd/src/components.js deleted file mode 100644 index 6653201..0000000 --- a/amd/src/components.js +++ /dev/null @@ -1,270 +0,0 @@ -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * Tiny C4L components. - * - * @module tiny_c4l/components - * @copyright 2022 Marc Català - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -const components = [ - { - id: "0", - name: "keyconcept", - type: "contextual", - imageClass: "c4l-keyconcept-icon", - code: - '

' + - "{{PLACEHOLDER}}


", - text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.", - variants: ["full-width"], - }, - { - id: "1", - name: "tip", - type: "contextual", - imageClass: "c4l-tip-icon", - code: - `

- {{PLACEHOLDER}}


`, - text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.", - variants: ["full-width"], - }, - { - id: "2", - name: "reminder", - type: "contextual", - imageClass: "c4l-reminder-icon", - code: - `

- {{PLACEHOLDER}}


`, - text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.", - variants: ["full-width"], - }, - { - id: "3", - name: "quote", - type: "contextual", - imageClass: "c4l-quote-icon", - code: - `

-
-

{{PLACEHOLDER}}

-
- {{VARIANTSHTML}} -


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus." + - " Praesent dictum in velit sed dapibus.", - variants: ["full-width", "quote"], - }, - { - id: "4", - name: "dodontcards", - type: "contextual", - imageClass: "c4l-dodontcards-icon", - code: - `

-
{{PLACEHOLDER}}
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Phasellus a posuere nibh, eu mollis lacus. - Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes, - nascetur ridiculus mus.


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus." + - " Praesent dictum in velit sed dapibus." + - "Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.", - variants: ["full-width"], - }, - { - id: "5", - name: "readingcontext", - type: "contextual", - imageClass: "c4l-readingcontext-icon", - code: - `

-

{{PLACEHOLDER}}

{{VARIANTSHTML}}


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus leo, hendrerit ac sem vitae," + - " posuere egestas nisi. Lorem ipsum dolor sit amet. " + - "Phasellus leo, hendrerit ac sem vitae, posuere egestas nisi.", - variants: ["full-width", "quote", "comfort-reading"], - }, - { - id: "6", - name: "example", - type: "contextual", - imageClass: "c4l-example-icon", - code: - `

Lorem ipsum dolor sit amet

-

{{PLACEHOLDER}}


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + - " Phasellus a posuere nibh, eu mollis lacus." + - " Praesent dictum in velit sed dapibus. Orci varius natoque penatibus et magnis dis parturient montes," + - " nascetur ridiculus mus.", - variants: ["full-width"], - }, - { - id: "7", - name: "figure", - type: "contextual", - imageClass: "c4l-figure-icon", - code: - `

Lorem ipsum dolor sit amet - {{VARIANTSHTML}}


`, - text: "Consectetur adipiscing elit.", - variants: ["full-width", "caption"], - }, - { - id: "8", - name: "tag", - type: "contextual", - imageClass: "c4l-tag-icon", - code: - `

-
{{PLACEHOLDER}}
`, - text: "Lorem ipsum", - variants: ["align-right"], - }, - { - id: "9", - name: "inlinetag", - type: "contextual", - imageClass: "c4l-inlinetag-icon", - code: `{{PLACEHOLDER}}`, - text: "Text", - variants: [], - }, - { - id: "10", - name: "attention", - type: "procedural", - imageClass: "c4l-attention-icon", - code: - `

- "{{PLACEHOLDER}}


`, - text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tempor odio vel turpis consequat sodales.", - variants: ["full-width"], - }, - { - id: "11", - name: "estimatedtime", - type: "procedural", - imageClass: "c4l-estimatedtime-icon", - code: - `

{{PLACEHOLDER}} {{#min}}
`, - text: "15", - variants: ["align-left"], - }, - { - id: "12", - name: "duedate", - type: "procedural", - imageClass: "c4l-duedate-icon", - code: - `

{{PLACEHOLDER}}
`, - text: "November 17th", - variants: ["align-left"], - }, - { - id: "13", - name: "proceduralcontext", - type: "procedural", - imageClass: "c4l-proceduralcontext-icon", - code: - `

- {{PLACEHOLDER}}


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus." + - " Praesent dictum in velit sed dapibus." + - " Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla quis lorem aliquet," + - " fermentum dolor ac, venenatis turpis.", - variants: ["full-width"], - }, - { - id: "14", - name: "learningoutcomes", - type: "procedural", - imageClass: "c4l-learningoutcomes-icon", - code: - `

-
-
{{#learningoutcomes}}
-
  • {{PLACEHOLDER}}
  • Curabitur non nulla sit amet - nisl tempus convallis quis ac lectus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.
  • -
  • Nulla porttitor accumsan tincidunt. Curabitur aliquet quam id dui posuere blandit. - Curabitur non nulla sit amet nisl tempus convallis quis ac lectus.


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut porta, neque id feugiat consectetur, " + - "enim ipsum tincidunt nunc, id suscipit mauris urna sit amet lectus.", - variants: ["full-width", "ordered-list"], - }, - { - id: "15", - name: "gradingvalue", - type: "evaluative", - imageClass: "c4l-gradingvalue-icon", - code: - `

{{#gradingvalue}}: {{PLACEHOLDER}}
`, - text: "33.3%", - variants: ["align-left"], - }, - { - id: "16", - name: "expectedfeedback", - type: "evaluative", - imageClass: "c4l-expectedfeedback-icon", - code: - `

-

{{PLACEHOLDER}}


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus." + - " Praesent dictum in velit sed dapibus.", - variants: ["full-width"], - }, - { - id: "17", - name: "allpurposecard", - type: "helper", - imageClass: "c4l-allpurposecard-icon", - code: - `

{{PLACEHOLDER}}


`, - text: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus a posuere nibh, eu mollis lacus." + - " Praesent dictum in velit sed dapibus." + - " Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.", - variants: ["full-width"], - }, -]; - -export default { - components, -}; diff --git a/amd/src/configuration.js b/amd/src/configuration.js index 794871b..18dabbe 100644 --- a/amd/src/configuration.js +++ b/amd/src/configuration.js @@ -14,14 +14,14 @@ // along with Moodle. If not, see . /** - * Tiny C4L configuration. + * Tiny Elements configuration. * - * @module tiny_c4l/configuration + * @module tiny_elements/configuration * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -import {component as c4lButtonName} from './common'; +import {component as elementsButtonName} from 'tiny_elements/common'; import {addMenubarItem} from 'editor_tiny/utils'; const configureMenu = (menu) => { @@ -29,7 +29,7 @@ const configureMenu = (menu) => { const inserted = items.some((item, index) => { // Append after the link button. if (item.match(/(link)\b/)) { - items.splice(index + 1, 0, c4lButtonName); + items.splice(index + 1, 0, elementsButtonName); return true; } @@ -39,7 +39,7 @@ const configureMenu = (menu) => { if (inserted) { menu.insert.items = items.join(' '); } else { - addMenubarItem(menu, 'insert', c4lButtonName); + addMenubarItem(menu, 'insert', elementsButtonName); } return menu; @@ -51,15 +51,16 @@ const configureToolbar = (toolbar) => { return toolbar.map((section) => { if (section.name === 'content') { - // Insert the c4l button at the start of it. - section.items.unshift(c4lButtonName); + // Insert the elements button at the start of it. + section.items.unshift(elementsButtonName); } return section; }); }; -export const configure = (instanceConfig) => { +export const configure = (instanceConfig, options) => { + instanceConfig.content_css.push(options.plugins['tiny_elements/plugin'].config.cssurl); return { menu: configureMenu(instanceConfig.menu), toolbar: configureToolbar(instanceConfig.toolbar), diff --git a/amd/src/data.js b/amd/src/data.js new file mode 100644 index 0000000..a84fb57 --- /dev/null +++ b/amd/src/data.js @@ -0,0 +1,266 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Container for tiny_elements data (categories, components, flavors, variants). + * + * @module tiny_elements/data + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {get_strings as getStrings} from 'core/str'; +import {component as pluginname} from 'tiny_elements/common'; +import { + variantExists, + setData as setVariantsData +} from './variantslib'; +import { + findById, + findByName +} from './helper'; +import {call as fetchMany} from 'core/ajax'; +import Log from 'core/log'; + +export default class Data { + categories = []; + components = []; + flavors = []; + variants = []; + langStrings = {}; + userStudent = false; + canManage = false; + contextid = 1; + + constructor(contextid, userStudent, previewElements, canManage) { + this.contextid = contextid; + this.userStudent = userStudent; + this.previewElements = previewElements; + this.canManage = canManage; + setVariantsData(this); + } + + async loadData() { + await this.loadElementsData(); + this.langStrings = await this.getAllStrings(); + } + + getComponents() { + return this.components; + } + + getFlavors() { + return this.flavors; + } + + getVariants() { + return this.variants; + } + + getComponentById(id) { + return findById(this.components, id); + } + + getCategoryFlavors(categoryname) { + const categoryFlavors = []; + this.flavors.forEach(flavor => { + if (flavor.categoryname == categoryname) { + categoryFlavors.push({ + id: flavor.id, + name: flavor.name, + displayname: flavor.displayname, + displayorder: flavor.displayorder, + }); + } + }); + return categoryFlavors; + } + + /** + * Get the Elements categories for the dialogue. + * + * @returns {object} data + */ + getCategories() { + const cats = []; + // Iterate over contexts. + this.categories.forEach((category) => { + let categoryFlavors = this.getCategoryFlavors(category.name); + categoryFlavors.sort((a, b) => a.displayorder - b.displayorder); + let hasFlavors = Array.isArray(categoryFlavors) && categoryFlavors.length; + cats.push({ + categoryid: category.id, + name: category.displayname, + categoryname: category.name, + type: category.id, + displayorder: category.displayorder, + flavors: categoryFlavors, + hasFlavors: hasFlavors, + active: '', + }); + }); + // Sort by displayorder and set first to active. + cats.sort((a, b) => a.displayorder - b.displayorder); + if (cats.length > 0) { + cats[0].active = 'active'; + if (cats[0].flavors.length > 0) { + cats[0].flavors[0].factive = 'active'; + } + } + + return cats; + } + + getComponentVariants(component) { + const componentVariants = []; + component.variants.forEach(variant => { + let variantitem = findByName(this.variants, variant); + if (variantitem !== undefined) { + let state = variantExists(component.name, variantitem.name) ? 'on' : 'off'; + componentVariants.push({ + id: variantitem.id, + name: variantitem.name, + displayname: variantitem.displayname, + state: state, + imageClass: variantitem.name + '-variant-' + state, + variantclass: (variantitem.c4lcompatibility ? 'c4l' : 'elements') + '-' + variantitem.name + '-variant', + title: this.langStrings.get(variantitem.name), + content: variantitem.content, + }); + } + }); + componentVariants.sort((a, b) => (a.name.localeCompare(b.name))); + return componentVariants; + } + + getCategoryById(id) { + return findById(this.categories, id); + } + + getLangString(id) { + return this.langStrings.get(id); + } + + /** + * Get the Elements buttons for the dialogue. + * + * @param {Editor} editor + * @returns {object} buttons + */ + getButtons(editor) { + const buttons = []; + // Not used at the moment. + // eslint-disable-next-line no-unused-vars + const sel = editor.selection.getContent(); + Object.values(this.components).forEach(component => { + buttons.push({ + id: component.id, + name: component.displayname, + type: component.categoryname, + imageClass: 'elements-' + component.name + '-icon', + htmlcode: component.code, + variants: this.getComponentVariants(component), + flavorlist: component.flavors.join(','), + category: component.categoryname, + displayorder: component.displayorder, + }); + }); + buttons.sort((a, b) => a.displayorder - b.displayorder); + + return buttons; + } + + /** + * Get the template context for the dialogue. + * + * @param {Editor} editor + * @returns {object} data + */ + getTemplateContext(editor) { + return Object.assign({}, { + elementid: editor.id, + buttons: this.getButtons(editor), + categories: this.getCategories(), + preview: this.previewElements, + canmanage: this.canManage, + }); + } + + getPreviewElements() { + return this.previewElements; + } + + /** + * Get language strings. + * + * @return {object} Language strings + */ + async getAllStrings() { + const keys = []; + const compRegex = /{{#([^}]*)}}/g; + + this.components.forEach(element => { + // Get lang strings from components. + [...element.code.matchAll(compRegex)].forEach(strLang => { + if (keys.indexOf(strLang[1]) === -1) { + keys.push(strLang[1]); + } + }); + + // Get lang strings from text placeholders. + [...element.text.matchAll(compRegex)].forEach(strLang => { + if (keys.indexOf(strLang[1]) === -1) { + keys.push(strLang[1]); + } + }); + }); + + const stringValues = await getStrings(keys.map((key) => ({key, pluginname}))); + return new Map(keys.map((key, index) => ([key, stringValues[index]]))); + } + + async loadElementsData() { + const data = await fetchMany([{ + methodname: 'tiny_elements_get_elements_data', + args: { + isstudent: this.userStudent, + contextid: this.contextid + }, + }])[0].catch(err => { + Log.error(err.message); + }); + + const indexedComponents = []; + data.components.forEach(component => { + indexedComponents[component.id] = component; + }); + + const indexedVariants = []; + data.variants.forEach(variant => { + indexedVariants[variant.id] = variant; + }); + + const indexedCategories = []; + data.categories.forEach(category => { + indexedCategories[category.id] = category; + }); + + this.components = indexedComponents; + this.variants = indexedVariants; + this.categories = indexedCategories; + this.flavors = data.flavors; + } +} diff --git a/amd/src/helper.js b/amd/src/helper.js new file mode 100644 index 0000000..8b8e6cb --- /dev/null +++ b/amd/src/helper.js @@ -0,0 +1,31 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Some helper functions for tiny_elements. + * + * @module tiny_elements/helper + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export const findById = (set, id) => { + return set.find((element) => element !== undefined && element.id == id); +}; + +export const findByName = (set, name) => { + return set.find((element) => element !== undefined && element.name == name); +}; diff --git a/amd/src/imagepicker.js b/amd/src/imagepicker.js new file mode 100644 index 0000000..2795370 --- /dev/null +++ b/amd/src/imagepicker.js @@ -0,0 +1,92 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Choose from images for iconurls. + * + * @module tiny_elements/imagepicker + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Modal from 'core/modal'; +import Ajax from 'core/ajax'; +import Templates from 'core/templates'; +import {getString} from 'core/str'; + +export const init = async(clickSelector, targetSelector) => { + const clickTargets = document.querySelectorAll(clickSelector); + + clickTargets.forEach((element) => { + let targetElement = element.closest('fieldset, form').querySelector(targetSelector); + + element.addEventListener('click', async() => { + // Let's see if we can figure out the category. + let categoryid = 0; + if (element.dataset.categoryid) { + categoryid = element.dataset.categoryid; + } else { + let categoryidelement = element.closest('form').querySelector('[name="compcat"], [name="categoryid"]'); + if (categoryidelement) { + categoryid = categoryidelement.value; + } + } + + let categoryname = ''; + if (element.dataset.categoryname) { + categoryname = element.dataset.categoryname; + } else { + let categoryelement = element.closest('form').querySelector('[name="categoryname"]'); + if (categoryelement) { + categoryname = categoryelement.value; + } + } + + const result = await Ajax.call([{ + methodname: 'tiny_elements_get_images', + args: { + contextid: 1, + categoryid: categoryid, + categoryname: categoryname, + }, + }])[0]; + + const renderedTemplate = await Templates.render('tiny_elements/imagepicker', { + images: result, + }); + + const pickerModal = await Modal.create({ + removeOnClose: true, + large: true, + body: renderedTemplate, + returnElement: element, + title: getString('showprinturls', 'tiny_elements'), + }); + + pickerModal.show(); + + const root = pickerModal.getRoot()[0]; + root.querySelectorAll('.tiny_elements_thumbnail').forEach((thumbnail) => { + thumbnail.addEventListener('click', (event) => { + const image = event.target.closest('img'); + const url = image.src; + targetElement.value = url; + pickerModal.hide(); + }); + }); + }); + }); +}; diff --git a/amd/src/management.js b/amd/src/management.js new file mode 100644 index 0000000..dc95319 --- /dev/null +++ b/amd/src/management.js @@ -0,0 +1,566 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Management functions for tiny_elements admin backend. + * + * @module tiny_elements/management + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {PreviewModal} from 'tiny_elements/previewmodal'; +import ModalForm from 'core_form/modalform'; +import Notification from 'core/notification'; +import {get_string as getString} from 'core/str'; +import {exception as displayException, deleteCancelPromise} from 'core/notification'; +import {call as fetchMany} from 'core/ajax'; +import {render as renderTemplate} from 'core/templates'; +import Log from 'core/log'; + +export const init = async(params) => { + + // Add listener to import xml files. + let importxml = document.getElementById('elements_import'); + importxml.addEventListener('click', async(e) => { + importModal(e); + }); + + // Add listener for adding a new item. + let additem = document.getElementsByClassName('add'); + additem.forEach(element => { + element.addEventListener('click', async(e) => { + showModal(e, element.dataset.id, element.dataset.table); + }); + }); + + // Add listener to edit items. + let edititems = document.getElementsByClassName('edit'); + edititems.forEach(element => { + element.addEventListener('click', async(e) => { + showModal(e, element.dataset.id, element.dataset.table); + }); + }); + + // Add listener to delete items. + let deleteitems = document.getElementsByClassName('delete'); + deleteitems.forEach(element => { + element.addEventListener('click', async(e) => { + deleteModal(e, element.dataset.id, element.dataset.title, element.dataset.table); + }); + }); + + // Add listener to preview items. + let previewitems = document.getElementsByClassName('preview-button'); + previewitems.forEach(element => { + element.addEventListener('click', async(e) => { + previewModal(e); + }); + }); + + // Add listener to select compcat to show corresponding items. + let compcats = document.getElementsByClassName('compcat'); + compcats.forEach(element => { + element.addEventListener('click', async(e) => { + showItems(e, element.dataset.compcat); + }); + }); + + // Add listener to edit licenses icon. + let editlicenses = document.getElementsByClassName('editlicenses'); + editlicenses.forEach(element => { + element.addEventListener('click', async(e) => { + editlicensesModal(e, element.dataset.id); + }); + }); + + // Add listener to manage component flavor relation. + let buttonicons = document.querySelectorAll('.buttonicons'); + buttonicons.forEach(element => { + element.addEventListener('click', async(e) => { + compflavorModal(e); + }); + }); + + let displaynamesbutton = document.getElementById('elements_displaynames_button'); + displaynamesbutton.addEventListener('click', async(e) => { + displaynamesModal(e); + }); + + let displaynamesflavorbutton = document.getElementById('elements_displaynames_flavor_button'); + displaynamesflavorbutton.addEventListener('click', async(e) => { + displaynamesFlavorModal(e); + }); + + let displaynamesvariantbutton = document.getElementById('elements_displaynames_variant_button'); + displaynamesvariantbutton.addEventListener('click', async(e) => { + displaynamesVariantModal(e); + }); + + // Add listener to duplicate items. + let duplicateitems = document.getElementsByClassName('duplicate'); + duplicateitems.forEach(element => { + element.addEventListener('click', async() => { + duplicateItem(element.dataset.id, element.dataset.table).always(() => reload()); + }); + }); + + // Add listener to wipe all items. + let wipebutton = document.getElementById('elements_wipe'); + if (wipebutton) { + wipebutton.addEventListener('click', async(e) => { + wipeModal(e); + }); + } + + // Add image and text to item setting click area. + let enlargeItems = document.querySelectorAll( + '.flavor .card-body > .clickingextended, .component .card-body > .clickingextended, .variant .card-body > .clickingextended' + ); + enlargeItems.forEach(element => { + element.addEventListener('click', async(e) => { + let item = e.target.closest('.item'); + item.querySelector('a.edit').click(); + }); + }); + + // After submitting a new item, reset active compcat. + if (params.compcatactive) { + let compcat = document.querySelector('.compcat[data-compcat="' + params.compcatactive + '"]'); + if (compcat) { + showItems(false, params.compcatactive); + compcat.classList.add('active'); + } + } +}; + +/** + * Show dynamic form to add/edit a source. + * @param {*} event + * @param {*} id + * @param {*} table + */ +const showModal = async(event, id, table) => { + event.preventDefault(); + let title; + if (id == 0) { + title = getString('additem', 'tiny_elements'); + } else { + title = getString('edititem', 'tiny_elements'); + } + + const modalForm = new ModalForm({ + // Set formclass, depending on component. + formClass: "tiny_elements\\form\\management_" + table + "_form", + args: { + id: id, + compcat: getActiveCompcatId(), + categoryname: getActiveCompcatName(), + }, + modalConfig: {title: title}, + returnFocus: event.target, + }); + // Conditional reload page after submit. + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => reload()); + + await modalForm.show(); +}; + +/** + * Show modal to preview css version. + * @param {*} event + */ +const previewModal = async(event) => { + event.preventDefault(); + let preview = event.target.closest(".preview-button"); + const modal = await PreviewModal.create({ + templateContext: { + component: preview.dataset.component, + flavors: preview.dataset.flavors.trim().split(" "), + config: M.cfg, + }, + }); + await modal.show(); +}; + +/** + * Show dynamic form to import xml backups. + * @param {*} event + */ +const importModal = async(event) => { + event.preventDefault(); + let title = getString('import', 'tiny_elements'); + + const modalForm = new ModalForm({ + // Load import form. + formClass: "tiny_elements\\form\\management_import_form", + args: {}, + modalConfig: {title: title}, + }); + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, importModalSubmitted); + + await modalForm.show(); +}; + +/** + * Process import form submit. + * @param {*} event + */ +const importModalSubmitted = async(event) => { + // Reload page after submit. + if (event.detail.update) { + location.reload(); + } else { + event.stopPropagation(); + renderTemplate('tiny_elements/management_import_form_result', event.detail).then(async(html) => { + await Notification.alert( + getString('import_simulation', 'tiny_elements'), + html, + getString('close', 'tiny_elements') + ); + return true; + }).catch((error) => { + displayException(error); + }); + } +}; + +/** + * Load modal to edit icon urls. + * @param {*} event + */ +const compflavorModal = async(event) => { + event.preventDefault(); + let title = getString('manage', 'tiny_elements'); + const target = event.target.closest('.buttonicons'); + const component = target.dataset.component ?? ''; + const flavor = target.dataset.flavor ?? ''; + const modalForm = new ModalForm({ + // Load import form. + formClass: "tiny_elements\\form\\management_comp_flavor_form", + args: { + component: component, + flavor: flavor, + }, + modalConfig: {title: title}, + }); + + await modalForm.show(); +}; + +/** + * Load modal to edit licenses of icons. + * @param {*} event + * @param {*} id + */ +const editlicensesModal = async(event, id) => { + event.preventDefault(); + let title = getString('editlicenses', 'tiny_elements'); + const modalForm = new ModalForm({ + formClass: "tiny_elements\\form\\management_editlicense_form", + args: { + id: id, + }, + modalConfig: {title: title}, + }); + await modalForm.show(); +}; + +/** + * Load modal to edit displaynames. + * @param {*} event + * @returns {void} + */ +const displaynamesModal = async(event) => { + event.preventDefault(); + let title = getString('manage', 'tiny_elements'); + + const modalForm = new ModalForm({ + // Load displaynames bulk edit form. + formClass: "tiny_elements\\form\\management_displaynames_form", + args: {}, + modalConfig: {title: title}, + }); + + // Reload page after submit. + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => location.reload()); + + await modalForm.show(); +}; + +/** + * Load modal to edit displaynames. + * @param {*} event + * @returns {void} + */ +const displaynamesFlavorModal = async(event) => { + event.preventDefault(); + let title = getString('manage', 'tiny_elements'); + + const modalForm = new ModalForm({ + // Load displaynames bulk edit form. + formClass: "tiny_elements\\form\\management_displaynames_flavors_form", + args: {}, + modalConfig: {title: title}, + }); + + // Reload page after submit. + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => location.reload()); + + await modalForm.show(); +}; + +/** + * Load modal to edit displaynames. + * @param {*} event + * @returns {void} + */ +const displaynamesVariantModal = async(event) => { + event.preventDefault(); + let title = getString('manage', 'tiny_elements'); + + const modalForm = new ModalForm({ + // Load displaynames bulk edit form. + formClass: "tiny_elements\\form\\management_displaynames_variants_form", + args: {}, + modalConfig: {title: title}, + }); + + // Reload page after submit. + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => location.reload()); + + await modalForm.show(); +}; + +/** + * Show dynamic form to delete a source. + * @param {*} event + * @param {*} id + * @param {*} title + * @param {*} table + */ +const deleteModal = (event, id, title, table) => { + event.preventDefault(); + + deleteCancelPromise( + getString('delete', 'tiny_elements', title), + getString('deletewarning', 'tiny_elements'), + ).then(async() => { + if (id !== 0) { + try { + const deleted = await deleteItem(id, table); + if (deleted) { + const link = document.querySelector('[data-table="' + table + '"][data-id="' + id + '"]'); + if (link) { + const card = link.closest(".item"); + card.remove(); + } + } + } catch (error) { + displayException(error); + } + } + return; + }).catch((err) => { + if (err.message) { + Log.error(err.message); + } + return; + }); +}; + +/** + * Show a modal to confirm wiping all items. + * @param {*} event + */ +const wipeModal = (event) => { + event.preventDefault(); + + deleteCancelPromise( + getString('wipe', 'tiny_elements'), + getString('wipewarning', 'tiny_elements') + ).then(async() => { + try { + await wipe(); + reload(); + } catch (error) { + displayException(error); + } + return; + }).catch((err) => { + if (err.message) { + Log.error(err.message); + } + return; + }); +}; + +/** + * Delete elements items. + * @param {*} id + * @param {*} table + * @returns {mixed} + */ +export const deleteItem = ( + id, + table, +) => fetchMany( + [{ + methodname: 'tiny_elements_delete_item', + args: { + id, + table, + } + }])[0]; + +/** + * Wipe all elements items. + * @returns {mixed} + */ +export const wipe = () => fetchMany( + [{ + methodname: 'tiny_elements_wipe', + args: { + "contextid": 1, + } + }])[0]; + +/** + * Show items after clicking a compcat. + * @param {*} event + * @param {*} compcat + */ +const showItems = (event, compcat) => { + // But first hide all items. + let itemsHide = document.querySelectorAll('.flavor, .component, .variant'); + itemsHide.forEach(element => { + element.classList.add('hidden'); + }); + + // Show component and variants with compcat name and read the flavors. + let itemsShow = document.querySelectorAll('[data-categoryname="' + compcat + '"]'); + let usedFlavors = []; + itemsShow.forEach(element => { + element.classList.remove('hidden'); + // Get all flavors to show if on compcat element. + if (typeof element.dataset.flavors !== 'undefined') { + let flavors = element.dataset.flavors.split(' '); + for (let value of flavors) { + if (!usedFlavors.includes(value) && value.length != 0) { + usedFlavors.push(value); + } + } + } + }); + + // Show the flavors. + let flavorstring = usedFlavors.map(item => `.${item}`).join(', '); + if (flavorstring.length) { + let flavorsShow = document.querySelectorAll(flavorstring); + flavorsShow.forEach(element => { + element.classList.remove('hidden'); + }); + } + + // Show add buttons. + let addsShow = document.getElementsByClassName('addcontainer'); + addsShow.forEach(element => { + element.classList.remove('hidden'); + }); + + // Unmark all and mark clicked compcat. + if (event) { + let items = document.getElementsByClassName('compcat'); + items.forEach(element => { + element.classList.remove('active'); + }); + let item = event.target.closest('.compcat'); + item.classList.add('active'); + } + + // Special case, unassigned items, show all items without connection to compcat. + if (compcat == 'found-items') { + let found = document.querySelector('.compcat[data-compcat="found-items"]'); + if (found.dataset.loneflavors.length) { + let flavorsShow = document.querySelectorAll(found.dataset.loneflavors); + flavorsShow.forEach(element => { + element.classList.remove('hidden'); + }); + } + if (found.dataset.lonevariants.length) { + let variantsShow = document.querySelectorAll(found.dataset.lonevariants); + variantsShow.forEach(element => { + element.classList.remove('hidden'); + }); + } + if (found.dataset.lonecomponents.length) { + let componentsShow = document.querySelectorAll(found.dataset.lonecomponents); + componentsShow.forEach(element => { + element.classList.remove('hidden'); + }); + } + } +}; + +/** + * Reload page with active compcat. + */ +const reload = () => { + // Reload page with active compcat. + const currentUrl = new URL(window.location.href); + currentUrl.searchParams.set('compcat', getActiveCompcatName()); + window.location.href = currentUrl.toString(); + window.location.reload(); +}; + +/** + * Get the current active compcat. + * @returns string Name of active compcat. + */ +const getActiveCompcatName = () => { + const compcat = document.querySelector('.compcat.active'); + if (!compcat) { + return ''; + } + return compcat.dataset.compcat ?? ''; +}; + +/** + * Get the current active compcat. + * @returns int Id of active compcat. + */ +const getActiveCompcatId = () => { + const compcat = document.querySelector('.compcat.active'); + if (!compcat) { + return 0; + } + return compcat.dataset.id ?? 0; +}; + +/** + * Duplicate elements items. + * @param {*} id + * @param {*} table + * @returns {mixed} + */ +export const duplicateItem = (id, table) => fetchMany( + [{ + methodname: 'tiny_elements_duplicate_item', + args: { + id, + table, + } + }])[0]; diff --git a/amd/src/modal.js b/amd/src/modal.js index 3dbbb66..8601c86 100644 --- a/amd/src/modal.js +++ b/amd/src/modal.js @@ -14,26 +14,27 @@ // along with Moodle. If not, see . /** - * C4L Modal for Tiny. + * Elements Modal for Tiny. * - * @module tiny_c4l/modal + * @module tiny_elements/modal * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Modal from 'core/modal'; -import ModalRegistry from 'core/modal_registry'; -const C4LModal = class extends Modal { - static TYPE = 'tiny_c4l/modal'; - static TEMPLATE = 'tiny_c4l/modal'; +export const ElementsModal = class extends Modal { + static TYPE = 'tiny_elements/modal'; + static TEMPLATE = 'tiny_elements/modal'; + + configure(modalConfig) { + // Remove modal from DOM on close. + modalConfig.removeOnClose = true; + super.configure(modalConfig); + } registerEventListeners() { // Call the parent registration. super.registerEventListeners(); } }; - -ModalRegistry.register(C4LModal.TYPE, C4LModal, C4LModal.TEMPLATE); - -export default C4LModal; diff --git a/amd/src/options.js b/amd/src/options.js index 9456439..3b78697 100644 --- a/amd/src/options.js +++ b/amd/src/options.js @@ -14,31 +14,25 @@ // along with Moodle. If not, see . /** - * Options helper for C4L plugin. + * Options helper for Elements plugin. * - * @module tiny_c4l/options + * @module tiny_elements/options * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {getPluginOptionName} from 'editor_tiny/options'; -import {pluginName} from './common'; +import {pluginName} from 'tiny_elements/common'; const isstudentName = getPluginOptionName(pluginName, 'isstudent'); -const allowedcompsName = getPluginOptionName(pluginName, 'allowedcomps'); const showpreviewName = getPluginOptionName(pluginName, 'showpreview'); -const viewc4lName = getPluginOptionName(pluginName, 'viewc4l'); -const previewCSS = getPluginOptionName(pluginName, 'previewcss'); -const customComps = getPluginOptionName(pluginName, 'customcomps'); +const viewElementsName = getPluginOptionName(pluginName, 'viewelements'); +const cssUrlName = getPluginOptionName(pluginName, 'cssurl'); +const canManageName = getPluginOptionName(pluginName, 'canmanage'); export const register = (editor) => { const registerOption = editor.options.register; - registerOption(allowedcompsName, { - processor: 'array', - "default": [], - }); - registerOption(isstudentName, { processor: 'boolean', "default": false, @@ -49,32 +43,32 @@ export const register = (editor) => { "default": true, }); - registerOption(viewc4lName, { + registerOption(viewElementsName, { processor: 'boolean', "default": true, }); - registerOption(previewCSS, { + registerOption(cssUrlName, { processor: 'string', - "default": '', + "default": '', }); - registerOption(customComps, { - processor: 'array', - "default": [], + registerOption(canManageName, { + processor: 'boolean', + "default": false, }); }; /** - * Get the permissions configuration for the Tiny C4L plugin. + * Get the permissions configuration for the Tiny Elements plugin. * * @param {TinyMCE} editor * @returns {object} */ -export const isC4LVisible = (editor) => editor.options.get(viewc4lName); +export const isElementsVisible = (editor) => editor.options.get(viewElementsName); /** - * Get whether user is a student configuration for the Tiny C4L plugin. + * Get whether user is a student configuration for the Tiny Elements plugin. * * @param {TinyMCE} editor * @returns {object} @@ -82,7 +76,7 @@ export const isC4LVisible = (editor) => editor.options.get(viewc4lName); export const isStudent = (editor) => editor.options.get(isstudentName); /** - * Get the preview visibility configuration for the Tiny C4L plugin. + * Get the preview visibility configuration for the Tiny Elements plugin. * * @param {TinyMCE} editor * @returns {object} @@ -90,25 +84,15 @@ export const isStudent = (editor) => editor.options.get(isstudentName); export const showPreview = (editor) => editor.options.get(showpreviewName); /** - * Get components allowed at students configuration for the Tiny C4L plugin. - * + * Get the css url for the Tiny Elements plugin (to be used in the editor). * @param {TinyMCE} editor - * @returns {object} - */ -export const getallowedComponents = (editor) => editor.options.get(allowedcompsName); - -/** - * Get custom components configuration for the Tiny C4L plugin. - * - * @param {TinyMCE} editor - * @returns {object} + * @returns {string} */ -export const getcustomComponents = (editor) => editor.options.get(customComps); +export const getCssUrl = (editor) => editor.options.get(cssUrlName); /** - * Get custom preview CSS configuration for the Tiny C4L plugin. - * + * Whether the use hat tiny_elements/manage capability. * @param {TinyMCE} editor - * @returns {string} + * @returns boolean */ -export const getpreviewCSS = (editor) => editor.options.get(previewCSS); +export const canManage = (editor) => editor.options.get(canManageName); diff --git a/amd/src/plugin.js b/amd/src/plugin.js index 84a8e93..a5cc0f4 100644 --- a/amd/src/plugin.js +++ b/amd/src/plugin.js @@ -14,9 +14,9 @@ // along with Moodle. If not, see . /** - * Tiny C4L plugin. + * Tiny Elements plugin. * - * @module tiny_c4l/plugin + * @module tiny_elements/plugin * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -24,11 +24,12 @@ import {getTinyMCE} from 'editor_tiny/loader'; import {getPluginMetadata} from 'editor_tiny/utils'; -import {component, pluginName} from './common'; -import {register as registerOptions} from './options'; -import {getSetup as getCommandSetup} from './commands'; -import * as Configuration from './configuration'; +import {component, pluginName} from 'tiny_elements/common'; +import {register as registerOptions} from 'tiny_elements/options'; +import {getSetup as getCommandSetup} from 'tiny_elements/commands'; +import * as Configuration from 'tiny_elements/configuration'; +// Setup the tiny_elements Plugin. // eslint-disable-next-line no-async-promise-executor export default new Promise(async(resolve) => { const [ @@ -51,6 +52,6 @@ export default new Promise(async(resolve) => { return pluginMetadata; }); - // Resolve the C4L Plugin and include configuration. + // Resolve the Elements Plugin and include configuration. resolve([pluginName, Configuration]); }); diff --git a/amd/src/preferencelib.js b/amd/src/preferencelib.js new file mode 100644 index 0000000..cbebcf8 --- /dev/null +++ b/amd/src/preferencelib.js @@ -0,0 +1,78 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Helper for handling user preferences. + * + * @module tiny_elements/preferencelib + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Ajax from 'core/ajax'; +import Notification from 'core/notification'; + +export const Preferences = { + category: 'elements_category', + // eslint-disable-next-line camelcase + category_flavors: 'elements_category_flavors', + // eslint-disable-next-line camelcase + component_variants: 'elements_component_variants' +}; + +/** + * Load user preferences. + * @param {string} name + * @returns {object} + */ +export const loadPreferences = async(name) => { + const request = { + methodname: 'core_user_get_user_preferences', + args: { + name: name + } + }; + + await Ajax.call([request])[0] + .then(result => { + try { + let preferences = JSON.parse(result.preferences[0].value); + return preferences; + } catch (err) { + Notification.exception(err); + return {}; + } + }).catch(err => { + Notification.exception(err); + return {}; + }); +}; + +/** + * Save user preferences. + * @param {object} rawPreferences + * @returns {Promise} + */ +export const savePreferences = (rawPreferences) => { + const request = { + methodname: 'core_user_update_user_preferences', + args: { + preferences: rawPreferences + } + }; + + return Ajax.call([request])[0].catch(Notification.exception); +}; diff --git a/amd/src/previewmodal.js b/amd/src/previewmodal.js new file mode 100644 index 0000000..0a08afd --- /dev/null +++ b/amd/src/previewmodal.js @@ -0,0 +1,35 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Component preview modal. + * + * @module tiny_elements/previewmodal + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Modal from 'core/modal'; + +export class PreviewModal extends Modal { + static TYPE = "tiny_elements/management_preview"; + static TEMPLATE = "tiny_elements/management_preview"; + configure(modalConfig) { + modalConfig.removeOnClose = true; + modalConfig.large = true; + super.configure(modalConfig); + } +} diff --git a/amd/src/ui.js b/amd/src/ui.js index bc67fb6..8e3a218 100644 --- a/amd/src/ui.js +++ b/amd/src/ui.js @@ -14,45 +14,45 @@ // along with Moodle. If not, see . /** - * Tiny C4L UI. + * Tiny Elements UI. * - * @module tiny_c4l/ui + * @module tiny_elements/ui * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -import {component} from './common'; -import C4LModal from './modal'; -import ModalFactory from 'core/modal_factory'; -import {components as Components} from './components'; -import {get_strings as getStrings} from 'core/str'; +import {ElementsModal} from 'tiny_elements/modal'; import { isStudent, - getallowedComponents, showPreview, - getpreviewCSS, - getcustomComponents -} from './options'; + canManage +} from 'tiny_elements/options'; import ModalEvents from 'core/modal_events'; import { addVariant, getVariantsClass, getVariantHtml, + getVariantPreferences, getVariantsHtml, loadVariantPreferences, removeVariant, - saveVariantPreferences, - variantExists -} from './variantslib'; - -let userStudent = false; -let previewC4L = true; -let allowedComponents = []; -let Contexts = []; -let langStrings = {}; -let previewCSS = ''; -let customComponents = []; -const compPrefix = 'c4lv-'; + setData as setVariantsData +} from 'tiny_elements/variantslib'; +import { + savePreferences, + loadPreferences, + Preferences +} from 'tiny_elements/preferencelib'; +import {getContextId} from 'editor_tiny/options'; +import Data from 'tiny_elements/data'; + +let currentFlavor = ''; +let currentFlavorId = 0; +let currentCategoryId = 1; +let currentCategoryName = ''; +let lastFlavor = []; +let selection = ''; +let data = {}; /** * Handle action @@ -60,14 +60,27 @@ const compPrefix = 'c4lv-'; * @param {TinyMCE} editor */ export const handleAction = async(editor) => { - userStudent = isStudent(editor); - previewC4L = showPreview(editor); - customComponents = getcustomComponents(editor); - addCustomComponents(); - allowedComponents = getallowedComponents(editor); - previewCSS = getpreviewCSS(editor); - langStrings = await getAllStrings(); - loadVariantPreferences(Components).then(() => displayDialogue(editor)); + selection = editor.selection.getContent(); + data = new Data( + getContextId(editor), + isStudent(editor), + showPreview(editor), + canManage(editor) + ); + await data.loadData(); + setVariantsData(data); + + currentCategoryId = loadPreferences(Preferences.category); + lastFlavor = loadPreferences(Preferences.category_flavors); + if (lastFlavor === null) { + lastFlavor = []; + } + let componentVariants = loadPreferences(Preferences.component_variants); + if (componentVariants === null) { + componentVariants = {}; + } + loadVariantPreferences(componentVariants); + await displayDialogue(editor); }; /** @@ -76,28 +89,20 @@ export const handleAction = async(editor) => { * @param {TinyMCE} editor */ const displayDialogue = async(editor) => { - const data = Object.assign({}, {}); - + const templateContext = data.getTemplateContext(editor); // Show modal with buttons. - const modal = await ModalFactory.create({ - type: C4LModal.TYPE, - templateContext: await getTemplateContext(editor, data), + const modal = await ElementsModal.create({ + type: ElementsModal.TYPE, + templateContext: templateContext, large: true, }); // Choose class to modal. - const modalClass = previewC4L ? 'c4l-modal' : 'c4l-modal-no-preview'; + const modalClass = data.getPreviewElements() ? 'elements-modal' : 'elements-modal-no-preview'; // Set class to modal. editor.targetElm.closest('body').classList.add(modalClass); - // Inject custom component styles in editor. - if (previewCSS !== "") { - const styles = document.createElement('style'); - styles.textContent = previewCSS; - editor.targetElm.closest('body').appendChild(styles); - } - modal.show(); // Event modal listener. @@ -105,25 +110,37 @@ const displayDialogue = async(editor) => { handleModalHidden(editor); }); - // Event filters listener. - const filters = modal.getRoot()[0].querySelectorAll('.c4l-button-filter'); - filters.forEach(node => { + // Event listener for categories without flavors. + const soleCategories = modal.getRoot()[0].querySelectorAll('.elements-category.no-flavors'); + soleCategories.forEach(node => { node.addEventListener('click', (event) => { - handleButtonFilterClick(event, modal); + handleCategoryClick(event, modal); }); }); - modal.getRoot()[0].querySelector('.c4l-select-filter').addEventListener('change', (event) => { - handleSelectFilterChange(event, modal); + // Event listener for categories with flavors. + const selectCategories = modal.getRoot()[0].querySelectorAll('.elements-category-flavor'); + selectCategories.forEach(node => { + node.addEventListener('click', (event) => { + handleCategoryFlavorClick(event, modal); + }); + }); + + // Event listener for category dropdown, triggering to switch to last used flavor. + const selectCategoriesRemember = modal.getRoot()[0].querySelectorAll('.nav-link.dropdown-toggle'); + selectCategoriesRemember.forEach(node => { + node.addEventListener('click', (event) => { + handleCategoryRemember(event, modal); + }); }); // Event buttons listeners. - const buttons = modal.getRoot()[0].querySelectorAll('.c4lt-dialog-button'); + const buttons = modal.getRoot()[0].querySelectorAll('.elementst-dialog-button'); buttons.forEach(node => { node.addEventListener('click', (event) => { handleButtonClick(event, editor, modal); }); - if (previewC4L) { + if (data.getPreviewElements()) { node.addEventListener('mouseenter', (event) => { handleButtonMouseEvent(event, modal, true); }); @@ -134,71 +151,129 @@ const displayDialogue = async(editor) => { }); // Event variants listeners. - const variants = modal.getRoot()[0].querySelectorAll('.c4l-button-variant'); + const variants = modal.getRoot()[0].querySelectorAll('.elements-button-variant'); variants.forEach(node => { node.addEventListener('click', (event) => { handleVariantClick(event, modal); }); - if (previewC4L) { - node.addEventListener('mouseenter', (event) => { - handleVariantMouseEvent(event, modal, true); + }); + + // Select first or saved category. + if (soleCategories.length > 0 || selectCategories.length > 0) { + const savedCategory = currentCategoryId; + const savedFlavor = lastFlavor[currentCategoryId]; + if (soleCategories.length == 0 || soleCategories[0].displayorder > selectCategories[0].displayorder) { + selectCategories[0].click(); + } else { + soleCategories[0].click(); + } + if (savedCategory != 0) { + soleCategories.forEach((node) => { + if (node.dataset.categoryid == savedCategory) { + node.click(); + } }); - node.addEventListener('mouseleave', (event) => { - handleVariantMouseEvent(event, modal, false); + selectCategories.forEach((node) => { + if (node.dataset.categoryid == savedCategory) { + node.click(); + } }); + if (savedFlavor) { + const flavorlink = modal.getRoot()[0].querySelector( + '.elements-category-flavor[data-id="' + savedFlavor + '"]' + ); + if (flavorlink) { + flavorlink.click(); + } + } } - }); + } }; /** - * Handle a change within filter select. + * Handle a click within filter button. * * @param {MouseEvent} event The change event * @param {obj} modal */ -const handleSelectFilterChange = (event, modal) => { - const select = event.target.closest('select'); - - if (select) { - const currentContext = select.value; - if (Contexts.indexOf(currentContext) !== -1) { - // Select current button. - const buttons = modal.getRoot()[0] - .querySelectorAll('.c4l-buttons-filters button'); - buttons.forEach(node => node.classList.remove('c4l-button-filter-enabled')); - const button = modal.getRoot()[0] - .querySelector('.c4l-button-filter[data-filter="' + currentContext + '"]'); - button.classList.add('c4l-button-filter-enabled'); - - // Show/hide component buttons. - showContextButtons(modal, currentContext); - } - } +const handleCategoryClick = (event, modal) => { + const link = event.target; + currentCategoryId = link.dataset.categoryid; + currentCategoryName = link.dataset.categoryname; + + // Remove active from all and set to selected. + const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item'); + links.forEach(node => node.classList.remove('active')); + link.classList.add('active'); + + // Show/hide component buttons. + showCategoryButtons(modal, currentCategoryName); }; /** - * Handle a click within filter button. + * Handle a click on a flavor in the category dropdown. * * @param {MouseEvent} event The change event * @param {obj} modal */ -const handleButtonFilterClick = (event, modal) => { - const button = event.target.closest('button'); - - const currentContext = button.dataset.filter; - // Filter button. - if (Contexts.indexOf(currentContext) !== -1) { - // Select current button. - const buttons = modal.getRoot()[0].querySelectorAll('.c4l-buttons-filters button'); - buttons.forEach(node => node.classList.remove('c4l-button-filter-enabled')); - button.classList.add('c4l-button-filter-enabled'); +const handleCategoryFlavorClick = (event, modal) => { + const link = event.target; + currentFlavor = link.dataset.flavor; + currentFlavorId = link.dataset.id; + currentCategoryId = link.dataset.categoryid; + currentCategoryName = link.dataset.categoryname; + lastFlavor[currentCategoryId] = currentFlavorId; + + // Remove active from all and set to selected. + const links = modal.getRoot()[0].querySelectorAll('.nav-link, .dropdown-item'); + links.forEach(node => node.classList.remove('active')); + link.classList.add('active'); + const category = modal.getRoot()[0].querySelector('.nav-link[data-categoryid="' + currentCategoryId + '"]'); + category.classList.add('active'); + + const componentButtons = modal.getRoot()[0].querySelectorAll('.elements-buttons-preview button'); + componentButtons.forEach(componentButton => { + // Remove previous flavor. + if (componentButton.dataset.flavor != undefined) { + componentButton.classList.remove(componentButton.dataset.flavor); + } + componentButton.classList.add(currentFlavor); + componentButton.dataset.flavor = currentFlavor; + if ( + (componentButton.dataset.flavorlist == '' || componentButton.dataset.flavorlist.split(',').includes(currentFlavor)) && + componentButton.dataset.category == currentCategoryName + ) { + componentButton.classList.remove('elements-hidden'); + if (componentButton.dataset.flavorlist != '') { + let variants = getVariantsClass(data.getComponentById(componentButton.dataset.id).name, currentFlavor); + let availableVariants = componentButton.querySelectorAll('.elements-button-variant'); + availableVariants.forEach((variant) => { + updateVariantButtonState(variant, variants.indexOf(variant.dataset.variantclass) != -1); + }); + } + } else { + componentButton.classList.add('elements-hidden'); + } + }); - // Select current option in select. - const select = modal.getRoot()[0].querySelector('.c4l-select-filter'); - select.selectedIndex = Contexts.indexOf(currentContext); +}; - // Show/hide component buttons. - showContextButtons(modal, currentContext); +/** + * When opening the category dropdown, try to load remembered flavor. + * + * @param {MouseEvent} event The change event + * @param {obj} modal + */ +const handleCategoryRemember = (event, modal) => { + const link = event.target; + currentCategoryId = link.dataset.categoryid; + currentCategoryName = link.dataset.categoryname; + currentFlavorId = lastFlavor[currentCategoryId]; + + if (currentFlavorId != undefined) { + // Call handleCategoryFlavorClick with tampered data. + let e = {target: modal.getRoot()[0].querySelector('.elements-category-flavor[data-id="' + currentFlavorId + '"]')}; + handleCategoryFlavorClick(e, modal); } }; @@ -208,8 +283,47 @@ const handleButtonFilterClick = (event, modal) => { * @param {obj} editor */ const handleModalHidden = (editor) => { - editor.targetElm.closest('body').classList.remove('c4l-modal-no-preview'); - saveVariantPreferences(Components); + editor.targetElm.closest('body').classList.remove('elements-modal-no-preview'); + if (currentCategoryId != 0 && currentFlavorId != 0) { + savePreferences([ + {type: Preferences.category, value: currentCategoryId}, + {type: Preferences.category_flavors, value: JSON.stringify(lastFlavor)}, + {type: Preferences.component_variants, value: JSON.stringify(getVariantPreferences())} + ]); + } +}; + +const updateComponentCode = (componentCode, selectedButton, placeholder, flavor = '') => { + componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder); + const comp = data.getComponentById(selectedButton); + // Return active variants for current component. + const variants = getVariantsClass(comp.name, flavor); + + // Apply variants to html component. + if (variants.length > 0) { + componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' ')); + componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(comp.name, flavor)); + } else { + componentCode = componentCode.replace('{{VARIANTS}}', ''); + componentCode = componentCode.replace('{{VARIANTSHTML}}', ''); + } + + if (currentFlavor) { + componentCode = componentCode.replace('{{FLAVOR}}', 'elements-' + currentFlavor + '-flavor'); + } else { + componentCode = componentCode.replace('{{FLAVOR}}', ''); + } + + componentCode = componentCode.replace('{{COMPONENT}}', 'elements-' + comp.name); + componentCode = componentCode.replace('{{CATEGORY}}', 'elements-' + data.getCategoryById(currentCategoryId).name); + + // Apply random IDs. + componentCode = applyRandomID(componentCode); + + // Apply lang strings. + componentCode = applyLangStrings(componentCode); + + return componentCode; }; /** @@ -219,41 +333,24 @@ const handleModalHidden = (editor) => { * @param {obj} editor * @param {obj} modal */ -const handleButtonClick = (event, editor, modal) => { +const handleButtonClick = async(event, editor, modal) => { const selectedButton = event.target.closest('button').dataset.id; + const comp = data.getComponentById(selectedButton); + // Component button. - const component = Components.find(element => element.name == selectedButton); - if (component != undefined) { - const sel = editor.selection.getContent(); - let componentCode = component.code; - const placeholder = (sel.length > 0 ? sel : component.text); + if (comp) { + let componentCode = comp.code; + const placeholder = (selection.length > 0 ? selection : comp.text); + + let flavor = comp.flavors.length > 0 ? currentFlavor : ''; // Create a new node to replace the placeholder. const randomId = generateRandomID(); const newNode = document.createElement('span'); newNode.dataset.id = randomId; newNode.innerHTML = placeholder; - componentCode = componentCode.replace('{{PLACEHOLDER}}', newNode.outerHTML); - - // Return active variants for current component. - const variants = getVariantsClass(component.name); - - // Apply variants to html component. - if (variants.length > 0) { - componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' ')); - componentCode = componentCode.replace('{{VARIANTSHTML}}', getVariantsHtml(component.name)); - } else { - componentCode = componentCode.replace('{{VARIANTS}}', ''); - componentCode = componentCode.replace('{{VARIANTSHTML}}', ''); - } - - // Apply random IDs. - componentCode = applyRandomID(componentCode); - - // Apply lang strings. - componentCode = applyLangStrings(componentCode); - + componentCode = updateComponentCode(componentCode, selectedButton, newNode.outerHTML, flavor); // Sets new content. editor.selection.setContent(componentCode); @@ -279,36 +376,25 @@ const handleButtonMouseEvent = (event, modal, show) => { const selectedButton = event.target.closest('button').dataset.id; const node = modal.getRoot()[0].querySelector('div[data-id="code-preview-' + selectedButton + '"]'); const previewDefault = modal.getRoot()[0].querySelector('div[data-id="code-preview-default"]'); + const comp = data.getComponentById(selectedButton); + + let flavor = comp.flavors.length > 0 ? currentFlavor : ''; + + const placeholder = (selection.length > 0 ? selection : comp.text); + + node.innerHTML = updateComponentCode(comp.code, selectedButton, placeholder, flavor); if (node) { if (show) { - previewDefault.classList.toggle('c4l-hidden'); - node.classList.toggle('c4l-hidden'); + previewDefault.classList.toggle('elements-hidden'); + node.classList.toggle('elements-hidden'); } else { - node.classList.toggle('c4l-hidden'); - previewDefault.classList.toggle('c4l-hidden'); + node.classList.toggle('elements-hidden'); + previewDefault.classList.toggle('elements-hidden'); } } }; -/** - * Handle a mouse events mouseenter/mouseleave in a variant button. - * - * @param {MouseEvent} event The mouseenter/mouseleave event - * @param {obj} modal - * @param {bool} show - */ -const handleVariantMouseEvent = (event, modal, show) => { - const variant = event.target.closest('span'); - const variantEnabled = variant.dataset.state == 'on'; - const button = event.target.closest('button'); - - if (!variantEnabled) { - updateVariantComponentState(variant, button, modal, show, false); - } -}; - - /** * Handle a mouse event within the variant buttons. * @@ -319,150 +405,18 @@ const handleVariantClick = (event, modal) => { event.stopPropagation(); const variant = event.target.closest('span'); const button = event.target.closest('button'); - updateVariantComponentState(variant, button, modal, false, true); -}; + const comp = data.getComponentById(button.dataset.id); + const flavor = comp.flavors.length > 0 ? currentFlavor : ''; -/** - * Get the template context for the dialogue. - * - * @param {Editor} editor - * @param {object} data - * @returns {object} data - */ -const getTemplateContext = async(editor, data) => { - return Object.assign({}, { - elementid: editor.id, - buttons: await getButtons(editor), - filters: await getFilters(), - preview: previewC4L, - }, data); -}; - -/** - * Get the C4L filters for the dialogue. - * - * @returns {object} data - */ -const getFilters = async() => { - const filters = []; - const stringValues = await getStrings(Contexts.map((key) => ({key, component}))); - - // Iterate over contexts. - Contexts.forEach((context, index) => { - filters.push({ - name: stringValues[index], - type: context, - filterClass: index === 0 ? 'c4l-button-filter-enabled' : '', - }); - }); - - return filters; -}; - -/** - * Get the C4L buttons for the dialogue. - * - * @param {Editor} editor - * @returns {object} buttons - */ -const getButtons = (editor) => { - const buttons = []; - const sel = editor.selection.getContent(); - let componentCode = ''; - let placeholder = ''; - let variants = []; - let buttonText = ''; - - // Iterate over components. - Components.forEach((component) => { - if (!userStudent || (userStudent && allowedComponents.includes(component.name))) { - if (previewC4L) { - placeholder = (sel.length > 0 ? sel : component.text); - componentCode = component.code; - componentCode = componentCode.replace('{{PLACEHOLDER}}', placeholder); - // Return active variants for component. - variants = getVariantsClass(component.name); - - // Apply class variants and html to html component. - const variantsNode = document.createElement('span'); - variantsNode.dataset.id = 'variantHTML-' + component.id; - if (variants.length > 0) { - componentCode = componentCode.replace('{{VARIANTS}}', variants.join(' ')); - variantsNode.innerHTML = getVariantsHtml(component.name); - componentCode = componentCode.replace('{{VARIANTSHTML}}', variantsNode.outerHTML); - } else { - componentCode = componentCode.replace('{{VARIANTS}}', ''); - componentCode = componentCode.replace('{{VARIANTSHTML}}', variantsNode.outerHTML); - } - - // Apply lang strings. - componentCode = applyLangStrings(componentCode); - } - - // Save contexts. - if (Contexts.indexOf(component.type) === -1) { - Contexts.push(component.type); - } - - buttonText = component.type == 'custom' ? component.buttonname : langStrings.get(component.name); - buttons.push({ - id: component.name, - name: buttonText, - type: component.type, - icon: component.icon ?? '', - imageClass: component.imageClass, - classComponent: compPrefix + component.name, - htmlcode: componentCode, - css: component.css ?? '', - variants: getVariantsState(component.name, component.variants), - }); - - // Add class to hide button. - if (Contexts.indexOf(component.type) !== 0) { - buttons[buttons.length - 1].imageClass += ' c4l-hidden'; - } - } - }); - - return buttons; -}; - -/** - * Get variants for the dialogue. - * - * @param {string} component - * @param {object} elements - * @return {object} Variants for a component - */ -const getVariantsState = (component, elements) => { - const variants = []; - let variantState = ''; - let variantClass = ''; - - // Max 3 variants. - if (elements.length > 3) { - elements = elements.slice(0, 2); - } - - elements.forEach((variant, index) => { - if (variantExists(component, variant)) { - variantState = 'on'; - variantClass = 'on '; - } else { - variantState = 'off'; - variantClass = ''; - } - variantClass += variant + '-variant-' + variantState; - variants.push({ - id: index, - name: variant, - state: variantState, - imageClass: variantClass, - title: langStrings.get(variant), - }); - }); + updateVariantComponentState(variant, button, modal, false, true); - return variants; + const node = modal.getRoot()[0].querySelector('div[data-id="code-preview-' + button.dataset.id + '"]'); + node.innerHTML = updateComponentCode( + comp.code, + button.dataset.id, + comp.text, + flavor + ); }; /** @@ -475,33 +429,35 @@ const getVariantsState = (component, elements) => { * @param {bool} updateHtml */ const updateVariantComponentState = (variant, button, modal, show, updateHtml) => { - const selectedVariant = 'c4l-' + variant.dataset.variant + '-variant'; - const component = Components.find(element => element.name == button.dataset.id); + const selectedVariant = variant.dataset.variantclass; + const selectedButton = button.dataset.id; const componentClass = button.dataset.classcomponent; const previewComponent = modal.getRoot()[0] .querySelector('div[data-id="code-preview-' + button.dataset.id + '"] .' + componentClass); const variantPreview = modal.getRoot()[0] .querySelector('span[data-id="variantHTML-' + button.dataset.id + '"]'); + const comp = data.getComponentById(selectedButton); let variantsHtml = ''; + let hasflavors = comp.flavors.length > 0; if (previewComponent) { if (updateHtml) { if (variant.dataset.state == 'on') { - removeVariant(component.name, variant.dataset.variant); + removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : ''); updateVariantButtonState(variant, false); previewComponent.classList.remove(selectedVariant); } else { - addVariant(component.name, variant.dataset.variant); + addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : ''); updateVariantButtonState(variant, true); previewComponent.classList.add(selectedVariant); } // Update variant preview HTML. if (variantPreview) { - variantPreview.innerHTML = getVariantsHtml(component.name); + variantPreview.innerHTML = getVariantsHtml(comp.name, currentFlavor); } } else { - variantsHtml = getVariantsHtml(component.name); + variantsHtml = getVariantsHtml(comp.name, currentFlavor); if (show) { previewComponent.classList.add(selectedVariant); variantsHtml += getVariantHtml(variant.dataset.variant); @@ -515,15 +471,13 @@ const updateVariantComponentState = (variant, button, modal, show, updateHtml) = } } } else { - if (updateHtml) { - // Update variants preferences. - if (variant.dataset.state == 'on') { - removeVariant(component.name, variant.dataset.variant); - updateVariantButtonState(variant, false); - } else { - addVariant(component.name, variant.dataset.variant); - updateVariantButtonState(variant, true); - } + // Update variants preferences. + if (variant.dataset.state == 'on') { + removeVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : ''); + updateVariantButtonState(variant, false); + } else { + addVariant(comp.name, variant.dataset.variant, hasflavors ? currentFlavor : ''); + updateVariantButtonState(variant, true); } } }; @@ -554,12 +508,12 @@ const updateVariantButtonState = (variant, activate) => { * @param {object} modal * @param {String} context */ -const showContextButtons = (modal, context) => { +const showCategoryButtons = (modal, context) => { const showNodes = modal.getRoot()[0].querySelectorAll('button[data-type="' + context + '"]'); const hideNodes = modal.getRoot()[0].querySelectorAll('button[data-type]:not([data-type="' + context + '"])'); - showNodes.forEach(node => node.classList.remove('c4l-hidden')); - hideNodes.forEach(node => node.classList.add('c4l-hidden')); + showNodes.forEach(node => node.classList.remove('elements-hidden')); + hideNodes.forEach(node => node.classList.add('elements-hidden')); }; /** @@ -572,7 +526,7 @@ const applyLangStrings = (text) => { const compRegex = /{{#([^}]*)}}/g; [...text.matchAll(compRegex)].forEach(strLang => { - text = text.replace('{{#' + strLang[1] + '}}', langStrings.get(strLang[1])); + text = text.replace('{{#' + strLang[1] + '}}', data.getLangString(strLang[1])); }); return text; @@ -601,83 +555,3 @@ const applyRandomID = (text) => { return text; }; - -/** - * Get language strings. - * - * @return {object} Language strings - */ -const getAllStrings = async() => { - const keys = []; - const compRegex = /{{#([^}]*)}}/g; - - Components.forEach(element => { - - // Only add name from standard components. - if (element.name.indexOf("customcomp") == -1) { - keys.push(element.name); - } - - // Get lang strings from variants. - element.variants.forEach(variant => { - if (keys.indexOf(variant) === -1) { - keys.push(variant); - } - }); - - // Get lang strings from components. - [...element.code.matchAll(compRegex)].forEach(strLang => { - if (keys.indexOf(strLang[1]) === -1) { - keys.push(strLang[1]); - } - }); - - // Get lang strings from text placeholders. - [...element.text.matchAll(compRegex)].forEach(strLang => { - if (keys.indexOf(strLang[1]) === -1) { - keys.push(strLang[1]); - } - }); - }); - - const stringValues = await getStrings(keys.map((key) => ({key, component}))); - return new Map(keys.map((key, index) => ([key, stringValues[index]]))); -}; - -/** - * Add custom components to standard components. - */ -const addCustomComponents = () => { - if (customComponents.length > 0) { - customComponents.forEach(customcomp => { - if (Components.find(element => element.id == customcomp['id'] + 1000) == undefined) { - Components.push({ - id: customcomp['id'] + 1000, - name: customcomp['name'], - buttonname: customcomp['buttonname'], - type: 'custom', - imageClass: 'c4l-custom-icon', - code: replaceCustomPlaceholders(customcomp), - text: customcomp['text'].length > 0 ? customcomp['text'] : '{{#textplaceholder}}', - variants: customcomp['variants'] ? ["full-width"] : [], - icon: customcomp['icon'], - css: customcomp['css'] - }); - } - }); - } -}; - -/** - * Replace custom placeholders with values. - * - * @param {object} component - * @return {string} HTML code. - */ -const replaceCustomPlaceholders = (component) => { - let html = component['code']; - const variants = component['variants'] ? " {{VARIANTS}}" : ""; - html = html.replace('{{CUSTOMCLASS}}', compPrefix + component['name'] + ' ' + compPrefix + "custom-component" + variants); - - return html; -}; diff --git a/amd/src/variants.js b/amd/src/variants.js deleted file mode 100644 index 2dc143f..0000000 --- a/amd/src/variants.js +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * Tiny C4L components variants. - * - * @module tiny_c4l/variants - * @copyright 2023 Marc Català - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -const variants = [ - { - id: 0, - name: "align-center", - html: "", - }, - { - id: 1, - name: "align-left", - html: "", - }, - { - id: 2, - name: "align-right", - html: "", - }, - { - id: 3, - name: "caption", - html: - `
Consectetur adipiscing elit. - Marcus Tullius Cicero, - De Finibus Bonorum et Malorum`, - }, -]; - -export default { - variants, -}; diff --git a/amd/src/variantslib.js b/amd/src/variantslib.js index 71089e9..85b35d0 100644 --- a/amd/src/variantslib.js +++ b/amd/src/variantslib.js @@ -14,132 +14,112 @@ // along with Moodle. If not, see . /** - * Variants helper for C4L plugin. + * Variants helper for Elements plugin. * - * @module tiny_c4l/variantslib + * @module tiny_elements/variantslib * @copyright 2023 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -import Ajax from 'core/ajax'; -import Notification from 'core/notification'; -import {variants as VARIANTS} from './variants'; +import { + findById, + findByName +} from 'tiny_elements/helper'; -const variantsPreferenceName = 'c4l_components_variants'; let variantPreferences = {}; +let data = {}; /** - * Load user preferences. - * - * @param {object} components [description] - * @returns {Promise} + * Set the data object. + * @param {object} values */ -export const loadVariantPreferences = async(components) => { +export const setData = (values) => { + data = values; +}; - const request = { - methodname: 'core_user_get_user_preferences', - args: { - name: variantsPreferenceName - } - }; - - return Ajax.call([request])[0] - .then(result => { - let comp = {}; - let rawPreferences = {}; - let variantComp = {}; - let variantObj = {}; - try { - rawPreferences = JSON.parse(result.preferences[0].value); - } catch (e) { - Notification.exception(e); - } - - if (rawPreferences !== null) { - Object.keys(rawPreferences).forEach(preference => { - comp = components.find(component => component.id == preference); - if (comp != undefined) { - variantPreferences[comp.name] = []; - rawPreferences[preference].forEach((variant) => { - variantObj = VARIANTS.find(element => element.id == variant); - if (variantObj != undefined) { - // Avoid invalid variants saved previously. - variantComp = comp.variants.find(element => element == variantObj.name); - if (variantComp != undefined) { - variantPreferences[comp.name].push(variantObj.name); - } - } - }); - } - }); - } - }).catch(Notification.exception); +/** + * Get the variant preferences. + * @returns {object} + */ +export const getVariantPreferences = () => { + return variantPreferences; }; /** - * Save user preferences. - * - * @param {object} components [description] + * Load user preferences. + * @param {array} preferences * @returns {Promise} */ -export const saveVariantPreferences = (components) => { - let comp = {}; - let rawPreferences = {}; - let variantObj = {}; - Object.keys(variantPreferences).forEach(preference => { - comp = components.find(component => component.name == preference); - if (comp != undefined) { - rawPreferences[comp.id] = []; - variantPreferences[preference].forEach((variant) => { - variantObj = VARIANTS.find(element => element.name == variant); - if (variantObj != undefined) { - rawPreferences[comp.id].push(variantObj.id); - } - }); - } - }); +export const loadVariantPreferences = (preferences) => { + if (preferences !== undefined && preferences !== null) { + variantPreferences = preferences; + } else { + variantPreferences = {}; + } +}; - const request = { - methodname: 'core_user_update_user_preferences', - args: { - preferences: [ - { - type: variantsPreferenceName, - value: JSON.stringify(rawPreferences) - } - ] - } - }; +/** + * Get variant preferences for a single component-flavor combination. + * @param {*} component + * @param {*} flavor + * @returns + */ +export const getVariantPreference = (component, flavor = '') => { + let componentObj = findByName(data.getComponents(), component); + let flavorObj = findByName(data.getFlavors(), flavor); - return Ajax.call([request])[0].catch(Notification.exception); -}; + if (componentObj === undefined) { + return []; + } + + if (flavor == '' && !variantPreferences[componentObj.id]) { + return []; + } + + if (flavor != '' && flavorObj === undefined) { + return []; + } + + if (flavor != '' && !variantPreferences[componentObj.id + '-' + flavorObj.id]) { + return []; + } + if (flavor == '') { + return variantPreferences[componentObj.id]; + } else { + return variantPreferences[componentObj.id + '-' + flavorObj.id]; + } +}; /** * Returns whether a variant exists for a component. * * @param {string} component Component name * @param {string} variant Variant name + * @param {string} flavor Flavor name * @returns {bool} */ -export const variantExists = (component, variant) => { - return variantPreferences?.[component] && variantPreferences[component].indexOf(variant) !== -1; +export const variantExists = (component, variant, flavor = '') => { + let variantObj = findByName(data.getVariants(), variant); + return getVariantPreference(component, flavor).indexOf(variantObj.id) !== -1; }; /** * Returns each variant for a component as a CSS class. * * @param {string} component Component name + * @param {string} flavor Flavor name * @returns {Array} */ -export const getVariantsClass = (component) => { +export const getVariantsClass = (component, flavor = '') => { let variants = []; + getVariantPreference(component, flavor).forEach(variant => { + let variantObj = findById(data.getVariants(), variant); + if (variantObj !== undefined) { + variants.push((variantObj.c4lcompatibility ? 'c4l' : 'elements') + '-' + variantObj.name + '-variant'); + } + }); - if (variantPreferences?.[component]) { - variantPreferences[component].forEach(variant => { - variants.push('c4l-' + variant + '-variant'); - }); - } return variants; }; @@ -147,20 +127,19 @@ export const getVariantsClass = (component) => { * Return all HTML variants for a component. * * @param {string} component Component name + * @param {string} flavor Flavor name * @returns {string} */ -export const getVariantsHtml = (component) => { +export const getVariantsHtml = (component, flavor) => { let variantsHtml = ''; - let variantObj = {}; - if (variantPreferences?.[component]) { - variantPreferences[component].forEach(variant => { - variantObj = VARIANTS.find(element => element.name == variant); - if (variantObj != undefined) { - variantsHtml += variantObj.html; - } - }); - } + getVariantPreference(component, flavor).forEach(variant => { + let variantObj = findById(data.getVariants(), variant); + if (variantObj !== undefined) { + variantsHtml += variantObj.content; + } + }); + return variantsHtml; }; @@ -174,7 +153,7 @@ export const getVariantHtml = (variant) => { let variantHtml = []; let variantObj = {}; - variantObj = VARIANTS.find(element => element.name == variant); + variantObj = findByName(data.getVariants(), variant); if (variantObj != undefined) { variantHtml = variantObj.html; } @@ -186,13 +165,27 @@ export const getVariantHtml = (variant) => { * * @param {string} component Component name * @param {string} variant Variant name + * @param {string} flavor Flavor name */ -export const addVariant = (component, variant) => { - if (!variantPreferences?.[component]) { - variantPreferences[component] = []; - } - if (!variantExists(component, variant)) { - variantPreferences[component].push(variant); +export const addVariant = (component, variant, flavor = '') => { + let componentObj = findByName(data.getComponents(), component); + let variantObj = findByName(data.getVariants(), variant); + let flavorObj = findByName(data.getFlavors(), flavor); + + if (flavor == '') { + if (!variantPreferences[componentObj.id]) { + variantPreferences[componentObj.id] = []; + } + if (!variantExists(component, variant)) { + variantPreferences[componentObj.id].push(variantObj.id); + } + } else { + if (!variantPreferences[componentObj.id + '-' + flavorObj.id]) { + variantPreferences[componentObj.id + '-' + flavorObj.id] = []; + } + if (!variantExists(component, variant, flavor)) { + variantPreferences[componentObj.id + '-' + flavorObj.id].push(variantObj.id); + } } }; @@ -201,10 +194,28 @@ export const addVariant = (component, variant) => { * * @param {string} component Component name * @param {string} variant Variant name + * @param {string} flavor Flavor name */ -export const removeVariant = (component, variant) => { - const index = variantPreferences[component].indexOf(variant); - if (index !== -1) { - delete variantPreferences[component][index]; +export const removeVariant = (component, variant, flavor = '') => { + let componentObj = findByName(data.getComponents(), component); + let variantObj = findByName(data.getVariants(), variant); + let flavorObj = findByName(data.getFlavors(), flavor); + + if (flavor != '') { + let index = variantPreferences[componentObj.id + '-' + flavorObj.id].indexOf(variantObj.id); + if (index !== -1) { + delete variantPreferences[componentObj.id + '-' + flavorObj.id][index]; + } + if (variantPreferences[componentObj.id + '-' + flavorObj.id].length == 0) { + delete variantPreferences[componentObj.id + '-' + flavorObj.id]; + } + } else { + let index = variantPreferences[componentObj.id].indexOf(variantObj.id); + if (index !== -1) { + delete variantPreferences[componentObj.id][index]; + } + if (variantPreferences[componentObj.id].length == 0) { + delete variantPreferences[componentObj.id]; + } } }; diff --git a/classes/exporter.php b/classes/exporter.php new file mode 100644 index 0000000..481ce8d --- /dev/null +++ b/classes/exporter.php @@ -0,0 +1,316 @@ +. + +namespace tiny_elements; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); +require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php'); +require_once($CFG->libdir . '/licenselib.php'); + +use tiny_elements\local\utils; +use tiny_elements\local\constants; +use core\exception\moodle_exception; +use memory_xml_output; +use xml_writer; + +/** + * Class exporter + * + * @package tiny_elements + * @author Stefan Hanauska + * @copyright 2025 ISB Bayern + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class exporter { + /** @var int $contextid */ + protected int $contextid; + + /** + * Constructor. + * + * @param int $contextid + */ + public function __construct(int $contextid = SYSCONTEXTID) { + $this->contextid = $contextid; + } + + /** + * Export. + * + * @param int $compcatid + * @return stored_file + * @throws moodle_exception + */ + public function export($compcatid = 0): \stored_file { + $fs = get_file_storage(); + $fp = get_file_packer('application/zip'); + + $exportfiles = $this->export_files($compcatid); + + $filerecord = [ + 'contextid' => $this->contextid, + 'component' => 'tiny_elements', + 'filearea' => 'export', + 'itemid' => time(), + 'filepath' => '/', + 'filename' => constants::FILE_NAME_EXPORT, + ]; + $exportxmlfile = $fs->create_file_from_string($filerecord, $this->exportxml($compcatid)); + $exportfiles[constants::FILE_NAME_EXPORT] = $exportxmlfile; + $filename = 'tiny_elements_export_' . time() . '.zip'; + $exportfile = $fp->archive_to_storage($exportfiles, $this->contextid, 'tiny_elements', 'export', 0, '/', $filename); + if (!$exportfile) { + throw new moodle_exception(get_string('error_export', 'tiny_elements')); + } + return $exportfile; + } + + /** + * Export files. + * + * @param int $compcatid + * @return array + */ + public function export_files(int $compcatid = 0): array { + global $DB; + $fs = get_file_storage(); + + $conditions = []; + $exportfiles = []; + + if (!empty($compcatid)) { + $conditions['id'] = $compcatid; + } + + $compcats = $DB->get_records('tiny_elements_compcat', $conditions); + + // It is necessary to get the files for each compcat separately to avoid mixing up files from + // different categories. + foreach ($compcats as $compcat) { + $files = $fs->get_area_files($this->contextid, 'tiny_elements', 'images', $compcat->id, 'itemid', false); + foreach ($files as $file) { + $exportfiles[$compcat->name . '/' . $file->get_filepath() . $file->get_filename()] = $file; + } + + // Write xml file with metadata. + $filerecord = [ + 'contextid' => $this->contextid, + 'component' => 'tiny_elements', + 'filearea' => 'export', + 'itemid' => time(), + 'filepath' => '/', + 'filename' => constants::FILE_NAME_METADATA . '_' . $compcat->name . '.xml', + ]; + $exportxmlfile = $fs->create_file_from_string($filerecord, $this->exportxml_filemetadata($compcat->id)); + $exportfiles[$compcat->name . '/' . $filerecord['filename']] = $exportxmlfile; + } + + return $exportfiles; + } + + /** + * Export XML. + * @param int $compcatid Category ID. + * @return string + */ + public function exportxml(int $compcatid = 0): string { + global $DB; + // Start. + $xmloutput = new memory_xml_output(); + $xmlwriter = new xml_writer($xmloutput); + $xmlwriter->start(); + $xmlwriter->begin_tag('elements'); + + $categoryname = ''; + if (!empty($compcatid)) { + $categoryname = $DB->get_field(constants::TABLES['compcat'], 'name', ['id' => $compcatid], MUST_EXIST); + } + + $result = $this->export_categories_and_components($xmlwriter, $categoryname); + + $this->export_flavors_and_variants($xmlwriter, $categoryname, $result['componentnames']); + + // End. + $xmlwriter->end_tag('elements'); + $xmlwriter->stop(); + $xmlstr = $xmloutput->get_allcontents(); + + // This is just here for compatibility reasons. + $xmlstr = utils::replace_pluginfile_urls($xmlstr); + + return $xmlstr; + } + + /** + * Export categories. + * + * @param xml_writer $xmlwriter + * @param string $categoryname + * @return array + */ + public function export_categories_and_components(xml_writer $xmlwriter, string $categoryname): array { + global $DB; + + $conditionscategories = []; + $conditionscomponents = []; + + if (!empty($categoryname)) { + $conditionscategories['name'] = $categoryname; + $conditionscomponents['categoryname'] = $categoryname; + } + + $compcats = $DB->get_records(constants::TABLES['compcat'], $conditionscategories); + $this->write_elements($xmlwriter, constants::TABLES['compcat'], $compcats); + + $components = $DB->get_records(constants::TABLES['component'], $conditionscomponents); + $this->write_elements($xmlwriter, constants::TABLES['component'], $components); + + return [ + 'componentnames' => array_column($components, 'name'), + ]; + } + + /** + * Export flavors and variants. + * + * @param xml_writer $xmlwriter + * @param string $categoryname + * @param array $componentnames + */ + public function export_flavors_and_variants( + xml_writer $xmlwriter, + string $categoryname = '', + array $componentnames = [] + ): void { + global $DB; + + $sql = ' = componentname'; + $params = []; + + if (!empty($categoryname)) { + [$sql, $params] = $DB->get_in_or_equal($componentnames, SQL_PARAMS_QM, 'param', true, ''); + } + $compflavors = $DB->get_records_sql( + "SELECT * FROM {" . constants::TABLES['compflavor'] . "} WHERE componentname " . $sql, + $params + ); + $this->write_elements($xmlwriter, constants::TABLES['compflavor'], $compflavors); + $flavornames = array_unique(array_column($compflavors, 'flavorname')); + + $sql = ' = name'; + if (!empty($categoryname)) { + [$sql, $params] = $DB->get_in_or_equal($flavornames, SQL_PARAMS_QM, 'param', true, ''); + } + $flavors = $DB->get_records_sql("SELECT * FROM {" . constants::TABLES['flavor'] . "} WHERE name " . $sql, $params); + $this->write_elements($xmlwriter, constants::TABLES['flavor'], $flavors); + + $sql = ' = componentname'; + if (!empty($categoryname)) { + [$sql, $params] = $DB->get_in_or_equal($componentnames, SQL_PARAMS_QM, 'param', true, '0'); + } + $compvariants = $DB->get_records_sql( + "SELECT * FROM {" . constants::TABLES['compvariant'] . "} WHERE componentname " . $sql, + $params + ); + $this->write_elements($xmlwriter, constants::TABLES['compvariant'], $compvariants); + $variantnames = array_unique(array_column($compvariants, 'variant')); + + $sql = ' = name'; + if (!empty($compcatid)) { + [$sql, $params] = $DB->get_in_or_equal($variantnames, SQL_PARAMS_QM, 'param', true, ''); + } + $variants = $DB->get_records_sql("SELECT * FROM {" . constants::TABLES['variant'] . "} WHERE name " . $sql, $params); + $this->write_elements($xmlwriter, constants::TABLES['variant'], $variants); + } + + /** + * Write elements. + * + * @param xml_writer $xmlwriter + * @param string $table + * @param array $data + */ + public function write_elements(xml_writer $xmlwriter, string $table, array $data): void { + global $DB; + + // Get columns. + $columns = $DB->get_columns($table); + + $xmlwriter->begin_tag($table); + foreach ($data as $value) { + $xmlwriter->begin_tag(constants::ITEMNAME); + foreach ($columns as $column) { + $name = $column->name; + $xmlwriter->full_tag($name, $value->$name ?? ''); + } + $xmlwriter->end_tag(constants::ITEMNAME); + } + $xmlwriter->end_tag($table); + } + + /** + * Export files metadata to XML. + * + * @param int $compcatid Category ID. + * @return string XML string. + */ + public function exportxml_filemetadata(int $compcatid): string { + global $DB; + + // Start. + $xmloutput = new memory_xml_output(); + $xmlwriter = new xml_writer($xmloutput); + $xmlwriter->start(); + $xmlwriter->begin_tag('tiny_elements_files_with_license'); + $compcatname = $DB->get_record(constants::TABLES['compcat'], ['id' => $compcatid], 'name'); + $xmlwriter->begin_tag($compcatname->name, ['id' => $compcatid]); + + // Get licenses. + $licenses = \license_manager::get_active_licenses(); + // Get files. + $fs = get_file_storage(); + $files = $fs->get_area_files(\context_system::instance()->id, 'tiny_elements', 'images', $compcatid, + "itemid, filepath, filename", false); + + foreach ($files as $file) { + $xmlwriter->begin_tag(constants::ITEMNAME); + + $xmlwriter->full_tag('id', $file->get_id() ?? ''); + $xmlwriter->full_tag('filename', $file->get_filename() ?? ''); + $xmlwriter->full_tag('source', $file->get_source() ?? ''); + $xmlwriter->full_tag('author', $file->get_author() ?? ''); + $xmlwriter->begin_tag('license'); + $xmlwriter->full_tag('shortname', $file->get_license() ?? ''); + $xmlwriter->full_tag('fullname', $licenses[$file->get_license()]->fullname ?? ''); + $xmlwriter->full_tag('source', $licenses[$file->get_license()]->source ?? ''); + $xmlwriter->end_tag('license'); + + $xmlwriter->end_tag(constants::ITEMNAME); + } + + // End. + $xmlwriter->end_tag($compcatname->name); + $xmlwriter->end_tag('tiny_elements_files_with_license'); + $xmlwriter->stop(); + $xmlstr = $xmloutput->get_allcontents(); + + return $xmlstr; + } +} diff --git a/classes/external/delete_item.php b/classes/external/delete_item.php new file mode 100644 index 0000000..6298696 --- /dev/null +++ b/classes/external/delete_item.php @@ -0,0 +1,95 @@ +. + +namespace tiny_elements\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; +use tiny_elements\manager; + +/** + * Webservice to delete tiny component entries. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delete_item extends external_api { + /** + * Describes the parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'id' => new external_value(PARAM_INT, 'Item id.', VALUE_REQUIRED), + 'table' => new external_value(PARAM_TEXT, 'Tablename.', VALUE_REQUIRED), + ]); + } + + /** + * Execute the service. + * + * @param int $id the id of the item to delete + * @param string $table the table to delete from + * @return array + * @throws invalid_parameter_exception + * @throws dml_exception + */ + public static function execute(int $id, string $table): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'id' => $id, + 'table' => $table, + ]); + $id = $params['id']; + $table = $params['table']; + $systemcontext = \context_system::instance(); + self::validate_context($systemcontext); + require_capability('tiny/elements:manage', $systemcontext); + + $manager = new manager(); + switch ($table) { + case 'compcat': + $manager->delete_compcat($id); + break; + case 'flavor': + $manager->delete_flavor($id); + break; + case 'variant': + $manager->delete_variant($id); + break; + case 'component': + $manager->delete_component($id); + break; + } + + return ['result' => true]; + } + + /** + * Describes the return structure of the service.. + * + * @return external_single_structure the return structure + */ + public static function execute_returns() { + return new external_single_structure([ + 'result' => new external_value(PARAM_BOOL, 'Removed successfully.'), + ]); + } +} diff --git a/classes/external/duplicate_item.php b/classes/external/duplicate_item.php new file mode 100644 index 0000000..54021e4 --- /dev/null +++ b/classes/external/duplicate_item.php @@ -0,0 +1,95 @@ +. + +namespace tiny_elements\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; +use tiny_elements\manager; + +/** + * Webservice to duplicate tiny_elements items. + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class duplicate_item extends external_api { + /** + * Describes the parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'id' => new external_value(PARAM_INT, 'Item id.', VALUE_REQUIRED), + 'table' => new external_value(PARAM_TEXT, 'Tablename.', VALUE_REQUIRED), + ]); + } + + /** + * Execute the service. + * + * @param int $id the id of the item to duplicate + * @param string $table the table of the item + * @return array + * @throws invalid_parameter_exception + * @throws dml_exception + */ + public static function execute(int $id, string $table): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'id' => $id, + 'table' => $table, + ]); + $id = $params['id']; + $table = $params['table']; + $systemcontext = \context_system::instance(); + self::validate_context($systemcontext); + require_capability('tiny/elements:manage', $systemcontext); + + $manager = new manager(); + switch ($table) { + case 'compcat': + $manager->duplicate_compcat($id); + break; + case 'flavor': + $manager->duplicate_flavor($id); + break; + case 'variant': + $manager->duplicate_variant($id); + break; + case 'component': + $manager->duplicate_component($id); + break; + } + + return ['result' => true]; + } + + /** + * Describes the return structure of the service.. + * + * @return external_single_structure the return structure + */ + public static function execute_returns() { + return new external_single_structure([ + 'result' => new external_value(PARAM_BOOL, 'Duplicated successfully.'), + ]); + } +} diff --git a/classes/external/get_elements_data.php b/classes/external/get_elements_data.php new file mode 100644 index 0000000..4bf3dda --- /dev/null +++ b/classes/external/get_elements_data.php @@ -0,0 +1,118 @@ +. + +namespace tiny_elements\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; + +/** + * Web service to retrieve all elements data. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_elements_data extends external_api { + /** + * Describes the parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'contextid' => new external_value(PARAM_INT, 'Context id', VALUE_REQUIRED), + 'isstudent' => new external_value(PARAM_BOOL, 'Only return components for students', VALUE_REQUIRED), + ]); + } + + /** + * Retrieve the categories, components, flavors and variants. + * @param int $contextid the context id (currently only system context is supported) + * @param bool $isstudent only return components for students + * @return array associative array containing the aggregated information for all elements data. + */ + public static function execute(int $contextid, bool $isstudent): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'contextid' => $contextid, + 'isstudent' => $isstudent, + ]); + $contextid = $params['contextid']; + $isstudent = $params['isstudent']; + $context = \core\context::instance_by_id($contextid); + self::validate_context($context); + + require_capability('tiny/elements:viewplugin', $context); + + return \tiny_elements\local\utils::get_elements_data($isstudent); + } + + /** + * Describes the return structure of the service. + * + * @return external_single_structure the return structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'categories' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'the id of the category'), + 'name' => new external_value(PARAM_TEXT, 'the name of the category'), + 'displayname' => new external_value(PARAM_TEXT, 'the display name of the category'), + 'displayorder' => new external_value(PARAM_INT, 'the display order of the category'), + ], 'a component category') + ), + 'components' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'the id of the component'), + 'name' => new external_value(PARAM_TEXT, 'the name of the component'), + 'displayname' => new external_value(PARAM_TEXT, 'the display name of the component'), + 'categoryname' => new external_value(PARAM_TEXT, 'the name of the component category'), + 'code' => new external_value(PARAM_RAW, 'the code'), + 'text' => new external_value(PARAM_TEXT, 'the text'), + 'variants' => new external_multiple_structure(new external_value(PARAM_TEXT, 'the variants')), + 'flavors' => new external_multiple_structure(new external_value(PARAM_TEXT, 'the flavors')), + 'displayorder' => new external_value(PARAM_INT, 'the display order of the component'), + ], 'a component') + ), + 'flavors' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'the id of the flavor'), + 'name' => new external_value(PARAM_TEXT, 'the name of the flavor'), + 'displayname' => new external_value(PARAM_TEXT, 'the display name of the flavor'), + 'categoryname' => new external_value(PARAM_TEXT, 'the name of the component category'), + 'displayorder' => new external_value(PARAM_INT, 'the display order of the flavor'), + 'content' => new external_value(PARAM_RAW, 'the content'), + 'categories' => new external_value(PARAM_TEXT, 'the categories'), + ], 'a component flavor') + ), + 'variants' => new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'the id of the variant'), + 'name' => new external_value(PARAM_TEXT, 'the name of the variant'), + 'displayname' => new external_value(PARAM_TEXT, 'the display name of the variant'), + 'categoryname' => new external_value(PARAM_TEXT, 'the name of the component category'), + 'content' => new external_value(PARAM_RAW, 'the content'), + 'c4lcompatibility' => new external_value(PARAM_BOOL, 'c4l compatibility'), + ], 'a component variant') + ), + ]); + } +} diff --git a/classes/external/get_flavors.php b/classes/external/get_flavors.php new file mode 100644 index 0000000..5ef21dd --- /dev/null +++ b/classes/external/get_flavors.php @@ -0,0 +1,86 @@ +. + +namespace tiny_elements\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; + +/** + * Web service to retrieve flavors data. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_flavors extends external_api { + /** + * Describes the parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'contextid' => new external_value(PARAM_INT, 'Context id', VALUE_REQUIRED), + 'categoryname' => new external_value(PARAM_TEXT, 'Category name', VALUE_REQUIRED), + 'query' => new external_value(PARAM_TEXT, 'Query string', VALUE_REQUIRED), + ]); + } + + /** + * Retrieve the flavors. + * @param int $contextid the context id (currently only system context is supported) + * @param string $categoryname the category name + * @param string $query the query string + * @return array associative array containing the aggregated information for all elements data. + */ + public static function execute(int $contextid, string $categoryname, string $query = ''): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'contextid' => $contextid, + 'categoryname' => $categoryname, + 'query' => $query, + ]); + $contextid = $params['contextid']; + $categoryname = $params['categoryname']; + $query = $params['query']; + $context = \core\context::instance_by_id($contextid); + self::validate_context($context); + + require_capability('tiny/elements:manage', $context); + + return \tiny_elements\local\utils::get_all_flavors(false, $categoryname, $query); + } + + /** + * Describes the return structure of the service. + * + * @return external_multiple_structure the return structure + */ + public static function execute_returns(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'the id of the variant'), + 'name' => new external_value(PARAM_TEXT, 'the name of the variant'), + 'displayname' => new external_value(PARAM_TEXT, 'the display name of the variant'), + 'categoryname' => new external_value(PARAM_TEXT, 'the category name of the variant'), + ], 'a component variant') + ); + } +} diff --git a/classes/external/get_images.php b/classes/external/get_images.php new file mode 100644 index 0000000..cedadb9 --- /dev/null +++ b/classes/external/get_images.php @@ -0,0 +1,84 @@ +. + +namespace tiny_elements\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; + +/** + * Web service to retrieve images. + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_images extends external_api { + /** + * Describes the parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'contextid' => new external_value(PARAM_INT, 'Context id', VALUE_REQUIRED), + 'categoryname' => new external_value(PARAM_TEXT, 'Category name', VALUE_OPTIONAL, 0), + 'categoryid' => new external_value(PARAM_INT, 'Category id', VALUE_OPTIONAL, 0), + ]); + } + + /** + * Retrieve the images. + * @param int $contextid the context id (currently only system context is supported) + * @param string $categoryname the category name + * @param int $categoryid the category id + * @return array list of images + */ + public static function execute(int $contextid, string $categoryname, int $categoryid): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'contextid' => $contextid, + 'categoryname' => $categoryname, + 'categoryid' => $categoryid, + ]); + $contextid = $params['contextid']; + $categoryname = $params['categoryname']; + $categoryid = $params['categoryid']; + $context = \core\context::instance_by_id($contextid); + self::validate_context($context); + + require_capability('tiny/elements:manage', $context); + + return \tiny_elements\local\utils::get_all_images($contextid, $categoryid, $categoryname); + } + + /** + * Describes the return structure of the service. + * + * @return external_multiple_structure the return structure + */ + public static function execute_returns(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure([ + 'url' => new external_value(PARAM_TEXT, 'the url of the image'), + 'name' => new external_value(PARAM_TEXT, 'the name of the image'), + ], 'images') + ); + } +} diff --git a/classes/external/get_variants.php b/classes/external/get_variants.php new file mode 100644 index 0000000..5729dd9 --- /dev/null +++ b/classes/external/get_variants.php @@ -0,0 +1,86 @@ +. + +namespace tiny_elements\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; + +/** + * Web service to retrieve variants data. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class get_variants extends external_api { + /** + * Describes the parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'contextid' => new external_value(PARAM_INT, 'Context id', VALUE_REQUIRED), + 'categoryname' => new external_value(PARAM_TEXT, 'Category name', VALUE_REQUIRED), + 'query' => new external_value(PARAM_TEXT, 'Query string', VALUE_REQUIRED), + ]); + } + + /** + * Retrieve the variants. + * @param int $contextid the context id (currently only system context is supported) + * @param string $categoryname the category name + * @param string $query the query string + * @return array associative array containing the aggregated information for all elements data. + */ + public static function execute(int $contextid, string $categoryname, string $query = ''): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'contextid' => $contextid, + 'categoryname' => $categoryname, + 'query' => $query, + ]); + $contextid = $params['contextid']; + $categoryname = $params['categoryname']; + $query = $params['query']; + $context = \core\context::instance_by_id($contextid); + self::validate_context($context); + + require_capability('tiny/elements:manage', $context); + + return \tiny_elements\local\utils::get_all_variants($categoryname, $query); + } + + /** + * Describes the return structure of the service. + * + * @return external_multiple_structure the return structure + */ + public static function execute_returns(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'the id of the variant'), + 'name' => new external_value(PARAM_TEXT, 'the name of the variant'), + 'displayname' => new external_value(PARAM_TEXT, 'the display name of the variant'), + 'categoryname' => new external_value(PARAM_TEXT, 'the category name of the variant'), + ], 'a component variant') + ); + } +} diff --git a/classes/external/wipe.php b/classes/external/wipe.php new file mode 100644 index 0000000..c770770 --- /dev/null +++ b/classes/external/wipe.php @@ -0,0 +1,79 @@ +. + +namespace tiny_elements\external; + +use tiny_elements\manager; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_api; +use core_external\external_value; + +/** + * Implementation of web service tiny_elements_wipe + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class wipe extends external_api { + + /** + * Describes the parameters for tiny_elements_wipe + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'contextid' => new external_value(PARAM_INT, 'Context id', VALUE_REQUIRED), + ]); + } + + /** + * Implementation of web service tiny_elements_wipe + * + * @param int $contextid the context id + * @return array result array + */ + public static function execute(int $contextid): array { + $params = self::validate_parameters(self::execute_parameters(), [ + 'contextid' => $contextid, + ]); + $contextid = $params['contextid']; + $context = \core\context::instance_by_id($contextid); + self::validate_context($context); + require_capability('tiny/elements:manage', $context); + + $manager = new manager($context->id); + $manager->wipe(); + + return [ + 'result' => true, + ]; + } + + /** + * Describe the return structure for tiny_elements_wipe + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'result' => new external_value(PARAM_BOOL, 'Result of the wipe operation'), + ]); + } +} diff --git a/classes/form/base_form.php b/classes/form/base_form.php new file mode 100644 index 0000000..fe03ddc --- /dev/null +++ b/classes/form/base_form.php @@ -0,0 +1,248 @@ +. + +namespace tiny_elements\form; + +use tiny_elements\local\utils; +use core_form\dynamic_form; +use context; +use tiny_elements\local\constants; + +/** + * Class base_form + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class base_form extends dynamic_form { + /** @var string $formtype */ + protected string $formtype; + /** + * Form definition. + */ + abstract public function definition(); + + /** + * Checks if Codemirror editor plugin is present + * + * @return bool + */ + protected function codemirror_present(): bool { + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_enabled_plugins('editor'); + return in_array('codemirror', $plugins); + } + + /** + * Returns context where this form is used + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * + * Checks if current user has sufficient permissions, otherwise throws exception + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('tiny/elements:manage', \context_system::instance()); + } + + /** + * Form validation. + * + * @param array $data array of ("fieldname"=>value) of submitted data + * @param array $files array of uploaded files "element_name"=>tmp_file_path + * @return array of "element_name"=>"error_description" if there are errors, + * or an empty array if everything is OK (true allowed for backwards compatibility too). + */ + public function validation($data, $files) { + $errors = []; + if (empty($data['name'])) { + $errors['name'] = get_string('errorname', 'tiny_elements'); + } + if (empty($data['displayname'])) { + $errors['displayname'] = get_string('errordisplayname', 'tiny_elements'); + } + if (array_key_exists('compcat', $data) && empty($data['compcat'])) { + $errors['compcat'] = get_string('errorcompcat', 'tiny_elements'); + } + return $errors; + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + global $DB; + + $table = 'tiny_elements_' . $this->formtype; + $context = $this->get_context_for_dynamic_submission(); + + $id = $this->optional_param('id', null, PARAM_INT); + $source = $DB->get_record($table, ['id' => $id]); + if (!$source) { + $source = new \stdClass(); + } + // Handle compcat images. + if ($this->formtype == 'compcat') { + $draftitemid = file_get_submitted_draft_itemid('compcatfiles'); + file_prepare_draft_area( + $draftitemid, + $context->id, + 'tiny_elements', + 'images', + $id, + constants::FILE_OPTIONS, + ); + $source->compcatfiles = $draftitemid; + } + + if ($this->formtype === 'component') { + if (isset($source->name)) { + $flavors = $DB->get_fieldset_select( + 'tiny_elements_comp_flavor', + 'flavorname', + 'componentname = ?', + ['componentname' => $source->name] + ); + $source->flavors = $flavors; + ; + $variants = $DB->get_fieldset_select( + 'tiny_elements_comp_variant', + 'variant', + 'componentname = ?', + ['componentname' => $source->name] + ); + $source->variants = $variants; + } + } + + $this->preprocess_editors($source); + + $this->set_data($source); + } + + /** + * Preprocess form data before loading the form + * + * @param object $formdata + */ + private function preprocess_editors(&$formdata) { + $form = $this->_form; + + if (isset($formdata->css)) { + $formdata->css = utils::replace_pluginfile_urls($formdata->css, true); + } + if (isset($formdata->js)) { + $formdata->js = utils::replace_pluginfile_urls($formdata->js, true); + } + if (isset($formdata->code)) { + $formdata->code = utils::replace_pluginfile_urls($formdata->code, true); + } + if (isset($formdata->content)) { + $formdata->content = utils::replace_pluginfile_urls($formdata->content, true); + } + if (isset($formdata->iconurl)) { + $formdata->iconurl = utils::replace_pluginfile_urls($formdata->iconurl, true); + $formdata->icongroup = ['iconurl' => $formdata->iconurl]; + } + + $cssel = array_key_exists('css', $form->_elementIndex) ? $form->_elements[$form->_elementIndex['css']] : null; + $jsel = array_key_exists('js', $form->_elementIndex) ? $form->_elements[$form->_elementIndex['js']] : null; + $htmlel = array_key_exists('content', $form->_elementIndex) ? $form->_elements[$form->_elementIndex['content']] : null; + $codeel = array_key_exists('code', $form->_elementIndex) ? $form->_elements[$form->_elementIndex['code']] : null; + + if ($cssel && $cssel->_type == 'editor') { + $formdata->css = [ + 'text' => $formdata->css, + 'format' => 90, + ]; + } + + if ($jsel && $jsel->_type == 'editor') { + $formdata->js = [ + 'text' => $formdata->js, + 'format' => 91, + ]; + } + + if ($htmlel && $htmlel->_type == 'editor') { + $formdata->content = [ + 'text' => $formdata->content, + 'format' => 92, + ]; + } + if ($codeel && $codeel->_type == 'editor') { + $formdata->code = [ + 'text' => $formdata->code, + 'format' => 92, + ]; + } + } + + /** + * Postprocess form data after form submission + * + * @param object $formdata + */ + protected function postprocess_editors(&$formdata) { + if (isset($formdata->css['text'])) { + $formdata->css = $formdata->css['text']; + } + if (isset($formdata->js['text'])) { + $formdata->js = $formdata->js['text']; + } + if (isset($formdata->content['text'])) { + $formdata->content = $formdata->content['text']; + } + if (isset($formdata->code['text'])) { + $formdata->code = $formdata->code['text']; + } + if (isset($formdata->css)) { + $formdata->css = utils::replace_pluginfile_urls($formdata->css); + } + if (isset($formdata->js)) { + $formdata->js = utils::replace_pluginfile_urls($formdata->js); + } + if (isset($formdata->code)) { + $formdata->code = utils::replace_pluginfile_urls($formdata->code); + } + if (isset($formdata->content)) { + $formdata->content = utils::replace_pluginfile_urls($formdata->content); + } + if (isset($formdata->iconurl)) { + $formdata->iconurl = utils::replace_pluginfile_urls($formdata->iconurl); + } + if (isset($formdata->icongroup['iconurl'])) { + $formdata->icongroup['iconurl'] = utils::replace_pluginfile_urls($formdata->icongroup['iconurl']); + $formdata->iconurl = $formdata->icongroup['iconurl']; + } + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + return new \moodle_url('/lib/editor/tiny/plugins/elements/management.php'); + } +} diff --git a/classes/form/management_comp_flavor_form.php b/classes/form/management_comp_flavor_form.php new file mode 100644 index 0000000..2dc73d6 --- /dev/null +++ b/classes/form/management_comp_flavor_form.php @@ -0,0 +1,191 @@ +. + +namespace tiny_elements\form; + +use core_form\dynamic_form; +use tiny_elements\local\utils; +use context; + +/** + * Class management_comp_flavor + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_comp_flavor_form extends dynamic_form { + /** + * Form definition + */ + public function definition() { + global $DB; + + $data = $this->_ajaxformdata; + + $conditions = []; + $categoryname = ''; + + if (!empty($data['component'])) { + $conditions['componentname'] = $data['component']; + $categoryname = $DB->get_field('tiny_elements_component', 'categoryname', ['name' => $data['component']]); + } + + if (!empty($data['flavor'])) { + $conditions['flavorname'] = $data['flavor']; + $categoryname = $DB->get_field('tiny_elements_flavor', 'categoryname', ['name' => $data['flavor']]); + } + + $count = $DB->count_records('tiny_elements_comp_flavor', $conditions); + $mform =& $this->_form; + + $mform->addElement('hidden', 'component'); + $mform->setType('component', PARAM_TEXT); + $mform->addElement('hidden', 'flavor'); + $mform->setType('flavor', PARAM_TEXT); + + $group = []; + $group[] = $mform->createElement('hidden', 'id'); + $group[] = $mform->createElement('static', 'name', get_string('component_flavor', 'tiny_elements')); + $subgroup = []; + $subgroup[] = $mform->createElement('text', 'iconurl', get_string('iconurl', 'tiny_elements')); + $subgroup[] = $mform->createElement( + 'button', + 'printurls', + get_string('showprinturls', 'tiny_elements'), + ['data-buttontype' => 'tiny_elements_printurls', 'data-categoryname' => $categoryname] + ); + $group[] = $mform->createElement('group', 'icongroup', get_string('iconurl', 'tiny_elements'), $subgroup, false); + + $options = [ + 'id' => [ + 'type' => PARAM_INT, + ], + 'name' => [ + 'type' => PARAM_TEXT, + ], + 'iconurl' => [ + 'type' => PARAM_URL, + ], + ]; + + $this->repeat_elements($group, $count, $options, 'itemcount', 'adddummy', 0); + + $mform->removeElement('adddummy'); + + $mform->setAttributes($mform->getAttributes() + ['data-formtype' => 'tiny_elements_comp_flavor']); + } + + /** + * Form definition after data is loaded. + */ + public function definition_after_data() { + global $PAGE; + parent::definition_after_data(); + $PAGE->requires->js_call_amd( + 'tiny_elements/imagepicker', + 'init', + ['[data-buttontype="tiny_elements_printurls"]', '[name*="iconurl"]'] + ); + } + + /** + * Returns context where this form is used + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * + * Checks if current user has sufficient permissions, otherwise throws exception + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('tiny/elements:manage', $this->get_context_for_dynamic_submission()); + } + + /** + * Form processing. + * + * @return array + */ + public function process_dynamic_submission(): array { + global $DB; + + $formdata = $this->get_data(); + + $result = true; + + foreach ($formdata->id as $key => $id) { + $record = new \stdClass(); + $record->id = $id; + $record->iconurl = utils::replace_pluginfile_urls($formdata->icongroup[$key]['iconurl'] ?? ''); + $result &= $DB->update_record('tiny_elements_comp_flavor', $record); + } + + // Purge CSS cache. + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + + return [ + 'update' => $result, + ]; + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + global $DB; + + $data = $this->_ajaxformdata; + + $conditions = []; + + if (!empty($data['component'])) { + $conditions['componentname'] = $data['component']; + } + + if (!empty($data['flavor'])) { + $conditions['flavorname'] = $data['flavor']; + } + + $compflavor = $DB->get_records('tiny_elements_comp_flavor', $conditions); + + $data = []; + foreach ($compflavor as $item) { + $data['id'][] = $item->id; + $data['name'][] = $item->componentname . '/' . $item->flavorname; + $data['icongroup'][] = ['iconurl' => utils::replace_pluginfile_urls($item->iconurl ?? '', true)]; + } + + $data['itemcount'] = count($compflavor); + + $this->set_data($data); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + return new \moodle_url('/lib/editor/tiny/plugins/elements/management.php'); + } +} diff --git a/classes/form/management_compcat_form.php b/classes/form/management_compcat_form.php new file mode 100644 index 0000000..5cb88a7 --- /dev/null +++ b/classes/form/management_compcat_form.php @@ -0,0 +1,80 @@ +. + +namespace tiny_elements\form; + +use tiny_elements\local\constants; +/** + * Class management_compcat_form + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_compcat_form extends base_form { + /** + * Form definition. + */ + public function definition() { + $mform =& $this->_form; + + // Set this variable to access correct db table. + $this->formtype = "compcat"; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('text', 'name', get_string('name', 'tiny_elements'), ['size' => '255']); + $mform->setType('name', PARAM_TEXT); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->addRule('name', get_string('validclassname', 'tiny_elements'), 'regex', '/^[_a-zA-Z][_a-zA-Z0-9-]*$/', 'client'); + + $mform->addElement('text', 'displayname', get_string('displayname', 'tiny_elements'), ['size' => '255']); + $mform->setType('displayname', PARAM_TEXT); + + $mform->addElement('text', 'displayorder', get_string('displayorder', 'tiny_elements')); + $mform->setType('displayorder', PARAM_INT); + + $mform->addElement($this->codemirror_present() ? 'editor' : 'textarea', 'css', get_string('css', 'tiny_elements')); + $mform->setType('css', PARAM_RAW); + + $mform->addElement('filemanager', 'compcatfiles', get_string('files', 'tiny_elements'), null, constants::FILE_OPTIONS); + } + + /** + * Process dynamic submission. + * + * @return array + */ + public function process_dynamic_submission(): array { + $context = $this->get_context_for_dynamic_submission(); + $formdata = $this->get_data(); + $this->postprocess_editors($formdata); + + $manager = new \tiny_elements\manager($context->id); + + if (empty($formdata->id)) { + $result = $manager->add_compcat($formdata); + } else { + $result = $manager->update_compcat($formdata); + } + + return [ + 'update' => $result, + ]; + } +} diff --git a/classes/form/management_component_form.php b/classes/form/management_component_form.php new file mode 100644 index 0000000..e699255 --- /dev/null +++ b/classes/form/management_component_form.php @@ -0,0 +1,145 @@ +. + +namespace tiny_elements\form; + +use tiny_elements\local\utils; + +/** + * Class management_component_form + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_component_form extends base_form { + /** + * Form definition. + */ + public function definition() { + global $DB; + + $compcats = $DB->get_records_menu('tiny_elements_compcat', null, 'displayname', 'name, displayname'); + $flavors = $DB->get_records_menu('tiny_elements_flavor', null, 'displayname', 'name, displayname'); + $variants = $DB->get_records_menu('tiny_elements_variant', null, 'displayname', 'name, displayname'); + + $mform =& $this->_form; + + // Set this variable to access correct db table. + $this->formtype = "component"; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('text', 'name', get_string('componentname', 'tiny_elements'), ['size' => '255']); + $mform->setType('name', PARAM_TEXT); + $mform->addHelpButton('name', 'componentname', 'tiny_elements'); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->addRule('name', get_string('validclassname', 'tiny_elements'), 'regex', '/^[_a-zA-Z][_a-zA-Z0-9-]*$/', 'client'); + + $mform->addElement('text', 'displayname', get_string('displayname', 'tiny_elements'), ['size' => '255']); + $mform->setType('displayname', PARAM_TEXT); + $mform->addHelpButton('displayname', 'displayname', 'tiny_elements'); + + $mform->addElement('select', 'categoryname', get_string('category', 'tiny_elements'), $compcats); + $mform->setType('categoryname', PARAM_TEXT); + if (!empty($this->_ajaxformdata['categoryname'])) { + $mform->setDefault('categoryname', $this->_ajaxformdata['categoryname']); + } + + $mform->addElement($this->codemirror_present() ? 'editor' : 'textarea', 'code', get_string('code', 'tiny_elements')); + $mform->setType('code', PARAM_RAW); + $mform->addHelpButton('code', 'code', 'tiny_elements'); + + $mform->addElement('textarea', 'text', get_string('text', 'tiny_elements')); + $mform->setType('text', PARAM_TEXT); + + $mform->addElement( + 'autocomplete', + 'variants', + get_string('variants', 'tiny_elements'), + $variants, + ['multiple' => true, 'ajax' => 'tiny_elements/category_form_helper'] + ); + $mform->setType('variants', PARAM_TEXT); + + $mform->addElement( + 'autocomplete', + 'flavors', + get_string('flavors', 'tiny_elements'), + $flavors, + ['multiple' => true, 'ajax' => 'tiny_elements/category_form_helper'] + ); + $mform->setType('flavors', PARAM_TEXT); + + $mform->addElement('text', 'displayorder', get_string('displayorder', 'tiny_elements')); + $mform->setType('displayorder', PARAM_INT); + + $mform->addElement($this->codemirror_present() ? 'editor' : 'textarea', 'css', get_string('css', 'tiny_elements')); + $mform->setType('css', PARAM_RAW); + + $group = []; + $group[] = $mform->createElement('text', 'iconurl', get_string('iconurl', 'tiny_elements')); + $group[] = $mform->createElement( + 'button', + 'printurls', + get_string('showprinturls', 'tiny_elements'), + ['data-buttontype' => 'tiny_elements_printurls'] + ); + $mform->setType('iconurl', PARAM_URL); + $mform->addElement('group', 'icongroup', get_string('iconurl', 'tiny_elements'), $group, false); + + $mform->addElement('checkbox', 'hideforstudents', get_string('hideforstudents', 'tiny_elements')); + $mform->setType('hideforstudents', PARAM_INT); + } + + /** + * Form definition after data is loaded. + */ + public function definition_after_data() { + global $PAGE; + parent::definition_after_data(); + $PAGE->requires->js_call_amd( + 'tiny_elements/imagepicker', + 'init', + ['[data-buttontype="tiny_elements_printurls"]', '[name*="iconurl"]'] + ); + } + + /** + * Process dynamic submission. + * + * @return array + */ + public function process_dynamic_submission(): array { + $context = $this->get_context_for_dynamic_submission(); + $formdata = $this->get_data(); + $this->postprocess_editors($formdata); + + $manager = new \tiny_elements\manager($context->id); + + if (empty($formdata->id)) { + $result = $manager->add_component($formdata); + } else { + $result = $manager->update_component($formdata); + } + + return [ + 'update' => $result, + ]; + } +} diff --git a/classes/form/management_displaynames_flavors_form.php b/classes/form/management_displaynames_flavors_form.php new file mode 100644 index 0000000..9d57fb5 --- /dev/null +++ b/classes/form/management_displaynames_flavors_form.php @@ -0,0 +1,132 @@ +. + +namespace tiny_elements\form; + +use core_form\dynamic_form; +use context; + +/** + * Form for bulk editing displaynames of flavors + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_displaynames_flavors_form extends dynamic_form { + /** + * Form definition + */ + public function definition() { + global $DB; + $count = $DB->count_records('tiny_elements_flavor'); + $mform =& $this->_form; + + $group = []; + $group[] = $mform->createElement('hidden', 'id'); + $group[] = $mform->createElement('static', 'name', get_string('flavor', 'tiny_elements')); + $group[] = $mform->createElement('text', 'displayname', get_string('displayname', 'tiny_elements')); + + $options = [ + 'id' => [ + 'type' => PARAM_INT, + ], + 'name' => [ + 'type' => PARAM_TEXT, + ], + 'displayname' => [ + 'type' => PARAM_TEXT, + ], + ]; + + $this->repeat_elements($group, $count, $options, 'itemcount', 'adddummy', 0); + + $mform->removeElement('adddummy'); + + $mform->setAttributes(['data-formtype' => 'tiny_elements_displaynames']); + } + + /** + * Returns context where this form is used + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * + * Checks if current user has sufficient permissions, otherwise throws exception + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('tiny/elements:manage', $this->get_context_for_dynamic_submission()); + } + + /** + * Form processing. + * + * @return array + */ + public function process_dynamic_submission(): array { + global $DB; + + $formdata = $this->get_data(); + + $result = true; + + foreach ($formdata->id as $key => $id) { + $record = new \stdClass(); + $record->id = $id; + $record->displayname = $formdata->displayname[$key]; + $result &= $DB->update_record('tiny_elements_flavor', $record); + } + + return [ + 'update' => $result, + ]; + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + global $DB; + + $flavors = $DB->get_records('tiny_elements_flavor'); + + $data = []; + foreach ($flavors as $flavor) { + $data['id'][] = $flavor->id; + $data['name'][] = $flavor->name; + $data['displayname'][] = $flavor->displayname; + } + + $data['itemcount'] = count($flavors); + + $this->set_data($data); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + return new \moodle_url('/lib/editor/tiny/plugins/elements/management.php'); + } +} diff --git a/classes/form/management_displaynames_form.php b/classes/form/management_displaynames_form.php new file mode 100644 index 0000000..c109cb4 --- /dev/null +++ b/classes/form/management_displaynames_form.php @@ -0,0 +1,134 @@ +. + +namespace tiny_elements\form; + +use core_form\dynamic_form; +use tiny_elements\local\utils; +use context; + +/** + * Form for bulk editing displaynames + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_displaynames_form extends dynamic_form { + /** + * Form definition + */ + public function definition() { + global $DB; + $count = $DB->count_records('tiny_elements_component'); + $mform =& $this->_form; + + $group = []; + $group[] = $mform->createElement('hidden', 'id'); + $group[] = $mform->createElement('static', 'longname', get_string('component', 'tiny_elements')); + $group[] = $mform->createElement('text', 'displayname', get_string('displayname', 'tiny_elements')); + + $options = [ + 'id' => [ + 'type' => PARAM_INT, + ], + 'longname' => [ + 'type' => PARAM_TEXT, + ], + 'displayname' => [ + 'type' => PARAM_TEXT, + ], + ]; + + $this->repeat_elements($group, $count, $options, 'itemcount', 'adddummy', 0); + + $mform->removeElement('adddummy'); + + $mform->setAttributes(['data-formtype' => 'tiny_elements_displaynames']); + } + + /** + * Returns context where this form is used + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * + * Checks if current user has sufficient permissions, otherwise throws exception + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('tiny/elements:manage', $this->get_context_for_dynamic_submission()); + } + + /** + * Form processing. + * + * @return array + */ + public function process_dynamic_submission(): array { + global $DB; + + $formdata = $this->get_data(); + + $result = true; + + foreach ($formdata->id as $key => $id) { + $record = new \stdClass(); + $record->id = $id; + $record->displayname = $formdata->displayname[$key]; + $result &= $DB->update_record('tiny_elements_component', $record); + } + + return [ + 'update' => $result, + ]; + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + global $DB; + + $components = $DB->get_records('tiny_elements_component'); + $categories = $DB->get_records('tiny_elements_compcat', null, '', 'name, id'); + + $data = []; + foreach ($components as $component) { + $data['id'][] = $component->id; + $data['longname'][] = $categories[$component->categoryname]->name . '/' . $component->name; + $data['displayname'][] = $component->displayname; + } + + $data['itemcount'] = count($components); + + $this->set_data($data); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + return new \moodle_url('/lib/editor/tiny/plugins/elements/management.php'); + } +} diff --git a/classes/form/management_displaynames_variants_form.php b/classes/form/management_displaynames_variants_form.php new file mode 100644 index 0000000..8744e1e --- /dev/null +++ b/classes/form/management_displaynames_variants_form.php @@ -0,0 +1,133 @@ +. + +namespace tiny_elements\form; + +use core_form\dynamic_form; +use tiny_elements\local\utils; +use context; + +/** + * Form for bulk editing displaynames of variants + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_displaynames_variants_form extends dynamic_form { + /** + * Form definition + */ + public function definition() { + global $DB; + $count = $DB->count_records('tiny_elements_variant'); + $mform =& $this->_form; + + $group = []; + $group[] = $mform->createElement('hidden', 'id'); + $group[] = $mform->createElement('static', 'name', get_string('variant', 'tiny_elements')); + $group[] = $mform->createElement('text', 'displayname', get_string('displayname', 'tiny_elements')); + + $options = [ + 'id' => [ + 'type' => PARAM_INT, + ], + 'name' => [ + 'type' => PARAM_TEXT, + ], + 'displayname' => [ + 'type' => PARAM_TEXT, + ], + ]; + + $this->repeat_elements($group, $count, $options, 'itemcount', 'adddummy', 0); + + $mform->removeElement('adddummy'); + + $mform->setAttributes(['data-formtype' => 'tiny_elements_displaynames']); + } + + /** + * Returns context where this form is used + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * + * Checks if current user has sufficient permissions, otherwise throws exception + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('tiny/elements:manage', $this->get_context_for_dynamic_submission()); + } + + /** + * Form processing. + * + * @return array + */ + public function process_dynamic_submission(): array { + global $DB; + + $formdata = $this->get_data(); + + $result = true; + + foreach ($formdata->id as $key => $id) { + $record = new \stdClass(); + $record->id = $id; + $record->displayname = $formdata->displayname[$key]; + $result &= $DB->update_record('tiny_elements_variant', $record); + } + + return [ + 'update' => $result, + ]; + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + global $DB; + + $variants = $DB->get_records('tiny_elements_variant'); + + $data = []; + foreach ($variants as $variant) { + $data['id'][] = $variant->id; + $data['name'][] = $variant->name; + $data['displayname'][] = $variant->displayname; + } + + $data['itemcount'] = count($variants); + + $this->set_data($data); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + return new \moodle_url('/lib/editor/tiny/plugins/elements/management.php'); + } +} diff --git a/classes/form/management_editlicense_form.php b/classes/form/management_editlicense_form.php new file mode 100644 index 0000000..a674f22 --- /dev/null +++ b/classes/form/management_editlicense_form.php @@ -0,0 +1,210 @@ +. + +/** + * Form for editing licenses. + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author 2025 Franziska Hübler + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tiny_elements\form; + +use core_form\dynamic_form; +use context; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/formslib.php'); +require_once($CFG->libdir . '/licenselib.php'); + +/** + * Form for editing licenses. + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author 2025 Franziska Hübler + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_editlicense_form extends dynamic_form { + + /** + * @var array $areafiles Files of category. + */ + private $areafiles = []; + + /** + * Define the form elements. + */ + public function definition(): void { + $data = $this->_ajaxformdata; + + $fs = get_file_storage(); + $this->areafiles = $fs->get_area_files(\context_system::instance()->id, 'tiny_elements', 'images', $data['id'], + "itemid, filepath, filename", false); + $count = count($this->areafiles); + + $mform =& $this->_form; + + $group = []; + // Fileicon. + $group[] = $mform->createElement('html', \html_writer::img('dummy', 'tiny_elements_thumbnail', + ['class' => 'tiny_elements_thumbnail'])); + // Filename. + $group[] = $mform->createElement('hidden', 'fileid'); + $group[] = $mform->createElement('static', 'filename', get_string('name', 'repository')); + // Author. + $group[] = $mform->createElement('text', 'fileauthor', get_string('author', 'repository')); + // Source. + $group[] = $mform->createElement('text', 'filesource', get_string('editlicensesformfileurl', 'tiny_elements')); + // License. + $licenses = []; + // Discard licenses without a name from enabled licenses. + foreach (\license_manager::get_active_licenses() as $license) { + if (!empty($license->fullname)) { + $licenses[$license->shortname] = $license->fullname; + } + } + $group[] = $mform->createElement('select', 'filelicense', get_string('license', 'repository'), $licenses); + + $options = [ + 'fileid' => [ + 'type' => PARAM_INT, + ], + 'filename' => [ + 'type' => PARAM_TEXT, + ], + 'fileauthor' => [ + 'type' => PARAM_TEXT, + 'helpbutton' => ['editlicensesformfileautor', 'tiny_elements'], + ], + 'filesource' => [ + 'type' => PARAM_TEXT, + 'helpbutton' => ['editlicensesformfileurl', 'tiny_elements'], + ], + 'filelicense' => [ + 'type' => PARAM_TEXT, + 'helpbutton' => ['editlicensesformfilelicense', 'tiny_elements'], + ], + ]; + + $this->repeat_elements($group, $count, $options, 'itemcount', 'adddummy', 0); + + $mform->removeElement('adddummy'); + } + + /** + * Returns context where this form is used. + * + * @return context Context where this form is used. + */ + protected function get_context_for_dynamic_submission(): context { + return \context_system::instance(); + } + + /** + * Checks if current user has sufficient permissions, otherwise throws exception. + */ + protected function check_access_for_dynamic_submission(): void { + require_capability('tiny/elements:manage', $this->get_context_for_dynamic_submission()); + } + + /** + * Process the form submission, used if form was submitted via AJAX. + * + * @return array Returns whether the records were updated. + */ + public function process_dynamic_submission(): array { + global $DB; + + $formdata = $this->get_data(); + $result = true; + + for ($i = 0; $i < $formdata->itemcount; $i++) { + $record = new \stdClass(); + $record->id = $formdata->fileid[$i]; + $record->author = $formdata->fileauthor[$i] ?? ''; + $record->license = $formdata->filelicense[$i] ?? ''; + $record->source = $formdata->filesource[$i] ?? ''; + $result &= $DB->update_record('files', $record, true); + } + + return [ + 'update' => $result, + ]; + } + + /** + * Load in existing data as form defaults. + */ + public function set_data_for_dynamic_submission(): void { + $data = $this->_ajaxformdata; + + $files = []; + foreach ($this->areafiles as $file) { + if ($file->is_directory()) { + continue; + } + $files['fileid'][] = $file->get_id(); + $files['filename'][] = $file->get_filename(); + $files['fileauthor'][] = $file->get_author(); + $files['filesource'][] = $file->get_source(); + $files['filelicense'][] = $file->get_license(); + $files['fileurl'][] = \moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename() + )->out(); + } + + $data['itemcount'] = count($files); + $this->set_data($files); + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): \moodle_url { + return new \moodle_url('/lib/editor/tiny/plugins/elements/management.php'); + } + + /** + * Called when data / defaults are already loaded. + * + * @return void + * @throws coding_exception + * @throws dml_exception + */ + public function definition_after_data(): void { + $mform = $this->_form; + $data = $mform->_defaultValues; + + for ($i = 0; $i < count($mform->_elements); $i++) { + if ($mform->_elements[$i]->_type === 'html') { + $mform->_elements[$i]->_text = \html_writer::img($data['fileurl'][intdiv($i, 6)], 'tiny_elements_thumbnail', + ['class' => 'tiny_elements_thumbnail']); + } + } + $mform->setAttributes(['data-formtype' => 'tiny_elements_editlicense']); + } +} diff --git a/classes/form/management_flavor_form.php b/classes/form/management_flavor_form.php new file mode 100644 index 0000000..c2eb2a0 --- /dev/null +++ b/classes/form/management_flavor_form.php @@ -0,0 +1,88 @@ +. + +namespace tiny_elements\form; + +/** + * Class management_flavor_form + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_flavor_form extends base_form { + /** + * Form definition. + */ + public function definition() { + global $DB; + $mform =& $this->_form; + + // Set this variable to access correct db table. + $this->formtype = "flavor"; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('text', 'name', get_string('name', 'tiny_elements'), ['size' => '255']); + $mform->setType('name', PARAM_TEXT); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->addRule('name', get_string('validclassname', 'tiny_elements'), 'regex', '/^[_a-zA-Z][_a-zA-Z0-9-]*$/', 'client'); + + $mform->addElement('text', 'displayname', get_string('displayname', 'tiny_elements'), ['size' => '255']); + $mform->setType('displayname', PARAM_TEXT); + + $compcats = $DB->get_records_menu('tiny_elements_compcat', null, 'displayname', 'name, displayname'); + $mform->addElement('select', 'categoryname', get_string('category', 'tiny_elements'), $compcats); + $mform->setType('categoryname', PARAM_TEXT); + if (!empty($this->_ajaxformdata['categoryname'])) { + $mform->setDefault('categoryname', $this->_ajaxformdata['categoryname']); + } + + $mform->addElement('text', 'displayorder', get_string('displayorder', 'tiny_elements'), ['size' => '3']); + $mform->setType('displayorder', PARAM_INT); + + $mform->addElement($this->codemirror_present() ? 'editor' : 'textarea', 'css', get_string('css', 'tiny_elements')); + $mform->setType('css', PARAM_RAW); + + $mform->addElement('checkbox', 'hideforstudents', get_string('hideforstudents', 'tiny_elements')); + $mform->setType('hideforstudents', PARAM_INT); + } + + /** + * Process dynamic submission. + * + * @return array + */ + public function process_dynamic_submission(): array { + $context = $this->get_context_for_dynamic_submission(); + $formdata = $this->get_data(); + $this->postprocess_editors($formdata); + + $manager = new \tiny_elements\manager($context->id); + + if (empty($formdata->id)) { + $result = $manager->add_flavor($formdata); + } else { + $result = $manager->update_flavor($formdata); + } + + return [ + 'update' => $result, + ]; + } +} diff --git a/classes/form/management_import_form.php b/classes/form/management_import_form.php new file mode 100644 index 0000000..5fadc65 --- /dev/null +++ b/classes/form/management_import_form.php @@ -0,0 +1,120 @@ +. + +namespace tiny_elements\form; + +use tiny_elements\local\constants; +use core\exception\moodle_exception; + +/** + * Class management_import_form + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_import_form extends base_form { + /** + * Form definition. + */ + public function definition() { + $mform =& $this->_form; + + $mform->addElement( + 'filepicker', + 'backupfile', + get_string('file'), + null, + constants::IMPORT_FILE_OPTIONS + ); + + $mform->addElement('advcheckbox', 'dryrun', get_string('dryrun', 'tiny_elements')); + $mform->addHelpButton('dryrun', 'dryrun', 'tiny_elements'); + } + + /** + * Form validation. + * + * @param array $data array of ("fieldname"=>value) of submitted data + * @param array $files array of uploaded files "element_name"=>tmp_file_path + * @return array of "element_name"=>"error_description" if there are errors, + * or an empty array if everything is OK (true allowed for backwards compatibility too). + */ + public function validation($data, $files) { + $errors = []; + if (empty($data['backupfile'])) { + $errors['backupfile'] = get_string('errorbackupfile', 'tiny_elements'); + } + return $errors; + } + + /** + * Process the form submission, used if form was submitted via AJAX + * + * @return array Returns whether a new source was created. + */ + public function process_dynamic_submission(): array { + $context = $this->get_context_for_dynamic_submission(); + $fs = get_file_storage(); + $data = $this->get_data(); + $this->postprocess_editors($data); + + $draftitemid = $data->backupfile; + file_save_draft_area_files( + $draftitemid, + $context->id, + 'tiny_elements', + 'import', + $draftitemid, + constants::IMPORT_FILE_OPTIONS + ); + $files = $fs->get_directory_files($context->id, 'tiny_elements', 'import', $draftitemid, '/', false, false); + do { + $file = array_pop($files); + } while ($file !== null && $file->is_directory()); + if ($file === null) { + throw new moodle_exception('errorbackupfile', 'tiny_elements'); + } + $dryrun = !empty($data->dryrun); + + $importer = new \tiny_elements\importer($context->id, $dryrun); + + if ($file->get_mimetype() == 'application/zip') { + $importer->import($file); + } else { + $xmlcontent = $file->get_content(); + $importer->importxml($xmlcontent); + } + + $return = ['update' => !$dryrun]; + + if ($dryrun) { + $results = $importer->get_importresults(); + $return['results'] = $results; + } + + $fs->delete_area_files($context->id, 'tiny_elements', 'import', $draftitemid); + + return $return; + } + + /** + * Load in existing data as form defaults + */ + public function set_data_for_dynamic_submission(): void { + } +} diff --git a/classes/form/management_variant_form.php b/classes/form/management_variant_form.php new file mode 100644 index 0000000..6a68482 --- /dev/null +++ b/classes/form/management_variant_form.php @@ -0,0 +1,114 @@ +. + +namespace tiny_elements\form; + +/** + * Class management_variant_form + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class management_variant_form extends base_form { + /** + * Form definition. + */ + public function definition() { + global $DB; + $mform =& $this->_form; + + // Set this variable to access correct db table. + $this->formtype = "variant"; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + $mform->addElement('text', 'name', get_string('name', 'tiny_elements'), ['size' => '255']); + $mform->setType('name', PARAM_TEXT); + $mform->addRule('name', get_string('required'), 'required', null, 'client'); + $mform->addRule('name', get_string('validclassname', 'tiny_elements'), 'regex', '/^[_a-zA-Z][_a-zA-Z0-9-]*$/', 'client'); + + $mform->addElement('text', 'displayname', get_string('displayname', 'tiny_elements'), ['size' => '255']); + $mform->setType('displayname', PARAM_TEXT); + + $compcats = $DB->get_records_menu('tiny_elements_compcat', null, 'displayname', 'name, displayname'); + $mform->addElement('select', 'categoryname', get_string('category', 'tiny_elements'), $compcats); + $mform->setType('categoryname', PARAM_TEXT); + if (!empty($this->_ajaxformdata['categoryname'])) { + $mform->setDefault('categoryname', $this->_ajaxformdata['categoryname']); + } + + $mform->addElement($this->codemirror_present() ? 'editor' : 'textarea', 'content', get_string('code', 'tiny_elements')); + $mform->setType('content', PARAM_RAW); + + $mform->addElement($this->codemirror_present() ? 'editor' : 'textarea', 'css', get_string('css', 'tiny_elements')); + $mform->setType('css', PARAM_RAW); + + $group = []; + $group[] = $mform->createElement('text', 'iconurl', get_string('iconurl', 'tiny_elements')); + $group[] = $mform->createElement( + 'button', + 'printurls', + get_string('showprinturls', 'tiny_elements'), + ['data-buttontype' => 'tiny_elements_printurls'] + ); + $mform->setType('iconurl', PARAM_URL); + $mform->addElement('group', 'icongroup', get_string('iconurl', 'tiny_elements'), $group, false); + + $mform->addElement('advcheckbox', 'c4lcompatibility', get_string('c4lcompatibility', 'tiny_elements')); + $mform->setType('c4lcompatibility', PARAM_INT); + $mform->setDefault('c4lcompatibility', 0); + $mform->addHelpButton('c4lcompatibility', 'c4lcompatibility', 'tiny_elements'); + } + + /** + * Form definition after data is loaded. + */ + public function definition_after_data() { + global $PAGE; + parent::definition_after_data(); + $PAGE->requires->js_call_amd( + 'tiny_elements/imagepicker', + 'init', + ['[data-buttontype="tiny_elements_printurls"]', '[name*="iconurl"]'] + ); + } + + /** + * Process dynamic submission. + * + * @return array + */ + public function process_dynamic_submission(): array { + $context = $this->get_context_for_dynamic_submission(); + $formdata = $this->get_data(); + $this->postprocess_editors($formdata); + + $manager = new \tiny_elements\manager($context->id); + + if (empty($formdata->id)) { + $result = $manager->add_variant($formdata); + } else { + $result = $manager->update_variant($formdata); + } + + return [ + 'update' => $result, + ]; + } +} diff --git a/classes/importer.php b/classes/importer.php new file mode 100644 index 0000000..4b99db2 --- /dev/null +++ b/classes/importer.php @@ -0,0 +1,600 @@ +. + +namespace tiny_elements; + +use tiny_elements\local\constants; +use tiny_elements\local\utils; +use core\exception\moodle_exception; + +/** + * Class importer + * + * @package tiny_elements + * @author Stefan Hanauska + * @copyright 2025 ISB Bayern + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class importer { + /** @var int $contextid */ + protected int $contextid; + + /** @var bool $dryrun If set to true, importer does a dry run and doesn't change anything in the database.*/ + protected bool $dryrun = false; + + /** @var array $importresults */ + protected array $importresults = []; + + /** + * Constructor. + * + * @param int $contextid + * @param bool $dryrun If true, the import process is simulated without any changes (default is false). + */ + public function __construct(int $contextid = SYSCONTEXTID, bool $dryrun = false) { + $this->contextid = $contextid; + $this->dryrun = $dryrun; + } + + /** + * Import data from a zip file. + * + * This method processes the provided zip file, extracts its contents, + * and imports the relevant XML and related category files. It also handles + * the cleanup of temporary files and rebuilds the system caches (CSS and JS). + * + * @param \stored_file|string $zip The zip file to import, either as a file object or path. + * @param int $draftitemid The draft item ID associated with the import process (default is 0). + + * @return void + */ + public function import(\stored_file|string $zip, int $draftitemid = 0): void { + global $DB; + + if ($zip instanceof \stored_file || file_exists($zip)) { + $fs = get_file_storage(); + $fp = get_file_packer('application/zip'); + $fp->extract_to_storage($zip, $this->contextid, 'tiny_elements', 'import', $draftitemid, '/'); + $xmlfile = $fs->get_file($this->contextid, 'tiny_elements', 'import', $draftitemid, '/', constants::FILE_NAME_EXPORT); + if (!$xmlfile) { + $xmlfile = $fs->get_file($this->contextid, 'tiny_elements', 'import', $draftitemid, '/', 'tiny_c4l_export.xml'); + } + $xmlcontent = $xmlfile->get_content(); + // Import data. + $categorymap = $this->importxml($xmlcontent); + // Import files. + list($incategoryids, $inparams) = $DB->get_in_or_equal(array_values($categorymap), SQL_PARAMS_NAMED); + $categories = $DB->get_records_select('tiny_elements_compcat', 'id ' . $incategoryids, $inparams, '', 'id, name'); + foreach ($categories as $category) { + $categoryfiles = $fs->get_directory_files( + $this->contextid, + 'tiny_elements', + 'import', + $draftitemid, + '/' . $category->name . '/', + true, + false + ); + $metadata = $this->update_files_metadata($category->name, $draftitemid); + $this->importfiles($categoryfiles, $category->id, $metadata, $category->name); + } + $fs->delete_area_files($this->contextid, 'tiny_elements', 'import', $draftitemid); + } + } + + /** + * Import files. + * + * @param array $files Array of stored_files. + * @param int $categoryid ID of the category. + * @param array $metadata Array of filemetadata objects. + * @param string $categoryname Name of the category. + * @throws moodle_exception + */ + public function importfiles(array $files, int $categoryid, array $metadata, string $categoryname = '', ): void { + $fs = get_file_storage(); + foreach ($files as $file) { + if ($file->is_directory()) { + continue; + } + if ($file->get_mimetype() == 'application/xml') { + $filename = $file->get_filename(); + if ($filename == constants::FILE_NAME_EXPORT || str_starts_with($filename, constants::FILE_NAME_METADATA)) { + continue; + } + } + $newfilepath = ($categoryname ? str_replace('/' . $categoryname, '', $file->get_filepath()) : $file->get_filepath()); + if ( + $oldfile = $fs->get_file( + $this->contextid, + 'tiny_elements', + 'images', + $categoryid, + $newfilepath, + $file->get_filename() + ) + ) { + if ($oldfile->get_contenthash() != $file->get_contenthash()) { + if (!$this->dryrun) { + $oldfile->replace_file_with($file); + } + $this->importresults[] = get_string('replacefile', 'tiny_elements', $newfilepath . $file->get_filename()); + } else { + $this->importresults[] = get_string('unchangedfile', 'tiny_elements', $newfilepath . $file->get_filename()); + } + } else { + if (!$this->dryrun) { + $filemetadata = []; + if (count($metadata) !== 0) { + // Find metadata for this file. + foreach ($metadata[$categoryname] as $metafile) { + if ($metafile->filename == $file->get_filename()) { + $filemetadata['source'] = $metafile->source; + $filemetadata['author'] = $metafile->author; + $filemetadata['license'] = $metafile->license; + break; + } + } + } + // Create file. + $filechanges = [ + 'contextid' => $this->contextid, + 'component' => 'tiny_elements', + 'filearea' => 'images', + 'itemid' => $categoryid, + 'filepath' => $newfilepath, + 'filename' => $file->get_filename(), + ]; + $newfile = $fs->create_file_from_storedfile(array_merge($filechanges, $filemetadata), $file); + if (!$newfile) { + throw new moodle_exception( + get_string('error_fileimport', 'tiny_elements', $newfilepath . $file->get_filename()) + ); + } + } + $this->importresults[] = get_string('newfile', 'tiny_elements', $newfilepath . $file->get_filename()); + } + } + } + + /** + * Load xml and import data. + * + * @param string $xmlcontent XML content to be imported. + * @return array $categorymap ID mapping of categories. + */ + public function importxml(string $xmlcontent): array { + try { + $xml = simplexml_load_string($xmlcontent); + } catch (\Exception $exception) { + $xml = false; + } + if (!$xml) { + return false; + } + + // Create mapping array for tiny_elements_compcat table. + $categorymap = []; + + // Create mapping array for tiny_elements_component table. + $componentmap = []; + + foreach (constants::TABLES as $table) { + $aliasname = constants::TABLE_ALIASES[$table]; + if (!isset($xml->$table) && !isset($xml->$aliasname) && !in_array($table, constants::OPTIONAL_TABLES)) { + throw new moodle_exception(get_string('error_import_missing_table', 'tiny_elements', $table)); + } + } + + $data = []; + + $aliases = array_flip(constants::TABLE_ALIASES); + + // Make data usable for further processing. + foreach ($xml as $table => $rows) { + foreach ($rows as $row) { + $obj = new \stdClass(); + foreach ($row as $column => $value) { + $obj->$column = (string) $value; + } + if (in_array($table, constants::TABLES)) { + $data[$table][] = $obj; + } else { + $data[$aliases[$table]][] = $obj; + } + } + } + + // First process all component categories. We need the category ids for the components. + foreach ($data['tiny_elements_compcat'] as $compcat) { + // Save new id for mapping. + $categorymap[$compcat->id] = self::import_category($compcat); + } + + foreach ($data['tiny_elements_component'] as $component) { + $componentmap[$component->id] = self::import_component($component, $categorymap); + } + + foreach ($data['tiny_elements_flavor'] as $flavor) { + self::import_flavor($flavor, $categorymap); + } + + foreach ($data['tiny_elements_variant'] as $variant) { + self::import_variant($variant, $categorymap); + } + + foreach ($data['tiny_elements_comp_flavor'] as $componentflavor) { + self::import_component_flavor($componentflavor, $categorymap); + } + + foreach ($data['tiny_elements_comp_variant'] as $componentvariant) { + self::import_component_variant($componentvariant, $componentmap); + } + + self::update_flavor_variant_category(); + + if (!$this->dryrun) { + local\utils::purge_and_rebuild_caches(); + } + + return $categorymap; + } + + /** + * Updates all flavors and variants that do not have a categoryname yet. + */ + public function update_flavor_variant_category(): void { + global $DB; + + $manager = new manager(); + + $flavors = $DB->get_records('tiny_elements_flavor', ['categoryname' => '']); + foreach ($flavors as $flavor) { + $categoryname = $manager->get_compcatname_for_flavor($flavor->name); + $DB->set_field('tiny_elements_flavor', 'categoryname', $categoryname, ['id' => $flavor->id]); + } + + $variants = $DB->get_records('tiny_elements_variant', ['categoryname' => '']); + foreach ($variants as $variant) { + $categoryname = $manager->get_compcatname_for_variant($variant->name); + $DB->set_field('tiny_elements_variant', 'categoryname', $categoryname, ['id' => $variant->id]); + } + } + + /** + * Import a component category. + * + * @param array|object $record + + * @return int id of the imported category + */ + public function import_category(array|object $record): int { + global $DB; + $record = (array) $record; + $oldid = $record['id']; + $current = $DB->get_record('tiny_elements_compcat', ['name' => $record['name']]); + if ($current) { + $record['id'] = $current->id; + if (!$this->dryrun) { + $DB->update_record('tiny_elements_compcat', $record); + } + $this->importresults[] = get_string('replacecategory', 'tiny_elements', $record['name']); + } else { + if (!$this->dryrun) { + $record['id'] = $DB->insert_record('tiny_elements_compcat', $record); + } else { + $record['id'] = rand(1, PHP_INT_MAX); + } + $this->importresults[] = get_string('newcategory', 'tiny_elements', $record['name']); + } + // Update pluginfile tags in css if the id has changed. + if ($oldid != $record['id'] && !$this->dryrun) { + $record['css'] = utils::update_pluginfile_tags($oldid, $record['id'], $record['css']); + $DB->update_record('tiny_elements_compcat', $record); + } + return $record['id']; + } + + /** + * Import a component. + * + * @param array|object $record + * @param array $categorymap + + * @return int id of the imported component + */ + public function import_component(array|object $record, array $categorymap): int { + global $DB; + $record = (array) $record; + if (array_key_exists('compcat', $record) && array_key_exists($record['compcat'], $categorymap)) { + $record['compcat'] = $categorymap[$record['compcat']]; + $record['categoryname'] = $DB->get_field('tiny_elements_compcat', 'name', ['id' => $record['compcat']]); + } + + $record['css'] = utils::update_pluginfile_tags_bulk($categorymap, $record['css'] ?? ''); + $record['code'] = utils::update_pluginfile_tags_bulk($categorymap, $record['code'] ?? ''); + $record['js'] = utils::update_pluginfile_tags_bulk($categorymap, $record['js'] ?? ''); + $record['iconurl'] = utils::update_pluginfile_tags_bulk($categorymap, $record['iconurl'] ?? ''); + + $current = $DB->get_record('tiny_elements_component', ['name' => $record['name']]); + if ($current) { + $record['id'] = $current->id; + if (!$this->dryrun) { + $DB->update_record('tiny_elements_component', $record); + } + $this->importresults[] = get_string('replacecomponent', 'tiny_elements', $record['name']); + } else { + try { + if (!$this->dryrun) { + $record['id'] = $DB->insert_record('tiny_elements_component', $record); + } else { + $record['id'] = rand(1, PHP_INT_MAX); + } + $this->importresults[] = get_string('newcomponent', 'tiny_elements', $record['name']); + } catch (\Exception $e) { + throw new moodle_exception(get_string('error_import_component', 'tiny_elements', $record['name'])); + } + } + + if (!$this->dryrun) { + if (!empty($record['flavors'])) { + foreach (explode(',', $record['flavors']) as $flavor) { + if ($flavor == '') { + continue; + } + $flavorrecord = [ + 'componentname' => $record['name'], + 'flavorname' => $flavor, + ]; + $existing = $DB->get_record('tiny_elements_comp_flavor', $flavorrecord); + if (!$existing) { + $DB->insert_record('tiny_elements_comp_flavor', $flavorrecord); + } + } + } + + if (!empty($record['variants'])) { + foreach (explode(',', $record['variants']) as $variant) { + if ($variant == '') { + continue; + } + $variantrecord = [ + 'componentname' => $record['name'], + 'variant' => $variant, + ]; + $existing = $DB->get_record('tiny_elements_comp_variant', $variantrecord); + if (!$existing) { + $DB->insert_record('tiny_elements_comp_variant', $variantrecord); + } + } + } + } + + return $record['id']; + } + + /** + * Import a flavor. + * + * @param array|object $record + * @param array $categorymap + * @return int id of the imported flavor + */ + public function import_flavor(array|object $record, array $categorymap): int { + global $DB; + $record = (array) $record; + $current = $DB->get_record('tiny_elements_flavor', ['name' => $record['name']]); + + $record['css'] = utils::update_pluginfile_tags_bulk($categorymap, $record['css'], 'import'); + $record['content'] = utils::update_pluginfile_tags_bulk($categorymap, $record['content'], 'import'); + + if ($current) { + $record['id'] = $current->id; + if (!$this->dryrun) { + $DB->update_record('tiny_elements_flavor', $record); + } + $this->importresults[] = get_string('replaceflavor', 'tiny_elements', $record['name']); + } else { + if (!$this->dryrun) { + $record['id'] = $DB->insert_record('tiny_elements_flavor', $record); + } else { + $record['id'] = rand(1, PHP_INT_MAX); + } + $this->importresults[] = get_string('newflavor', 'tiny_elements', $record['name']); + } + return $record['id']; + } + + /** + * Import a variant. + * + * @param array|object $record + * @param array $categorymap + * @return int id of the imported variant + */ + public function import_variant(array|object $record, array $categorymap): int { + global $DB; + $record = (array) $record; + $current = $DB->get_record('tiny_elements_variant', ['name' => $record['name']]); + + $record['css'] = utils::update_pluginfile_tags_bulk($categorymap, $record['css'] ?? ''); + $record['content'] = utils::update_pluginfile_tags_bulk($categorymap, $record['content'] ?? ''); + $record['iconurl'] = utils::update_pluginfile_tags_bulk($categorymap, $record['iconurl'] ?? ''); + + if ($current) { + $record['id'] = $current->id; + if (!$this->dryrun) { + $DB->update_record('tiny_elements_variant', $record); + } + $this->importresults[] = get_string('replacevariant', 'tiny_elements', $record['name']); + } else { + if (!$this->dryrun) { + $record['id'] = $DB->insert_record('tiny_elements_variant', $record); + } else { + $record['id'] = rand(1, PHP_INT_MAX); + } + $this->importresults[] = get_string('newvariant', 'tiny_elements', $record['name']); + } + return $record['id']; + } + + /** + * Import a relation between component and flavor. + * + * @param array|object $record + * @param array $categorymap + * @return int id of the imported relation + */ + public function import_component_flavor(array|object $record, array $categorymap): int { + global $DB; + $record = (array) $record; + $current = $DB->get_record( + 'tiny_elements_comp_flavor', + ['componentname' => $record['componentname'], 'flavorname' => $record['flavorname']] + ); + + $record['iconurl'] = utils::update_pluginfile_tags_bulk($categorymap, $record['iconurl'] ?? ''); + + if ($current) { + $record['id'] = $current->id; + if (!$this->dryrun) { + $DB->update_record('tiny_elements_comp_flavor', $record); + } + $this->importresults[] = get_string( + 'replacecompflavor', + 'tiny_elements', + $record['componentname'] . ' - ' . $record['flavorname'] + ); + } else { + if (!$this->dryrun) { + $record['id'] = $DB->insert_record('tiny_elements_comp_flavor', $record); + } else { + $record['id'] = rand(1, PHP_INT_MAX); + } + $this->importresults[] = get_string( + 'newcompflavor', + 'tiny_elements', + $record['componentname'] . ' - ' . $record['flavorname'] + ); + } + return $record['id']; + } + + /** + * Import a relation between component and variant. + * + * @param array|object $record + * @param array $componentmap + * @return int id of the imported relation + */ + public function import_component_variant(array|object $record, array $componentmap): int { + global $DB; + $record = (array) $record; + + if (!array_key_exists('componentname', $record)) { + // Do not import relations for components that are not part of the import. + if (!isset($componentmap[$record['component']])) { + return 0; + } + $record['component'] = $componentmap[$record['component']]; + $record['componentname'] = $DB->get_field( + 'tiny_elements_component', + 'name', + ['id' => $record['component']] + ); + } + + $current = $DB->get_record( + 'tiny_elements_comp_variant', + ['componentname' => $record['componentname'], 'variant' => $record['variant']] + ); + if (!$current) { + if (!$this->dryrun) { + $record['id'] = $DB->insert_record('tiny_elements_comp_variant', $record); + } else { + $record['id'] = rand(1, PHP_INT_MAX); + } + $this->importresults[] = get_string( + 'newcompvariant', + 'tiny_elements', + $record['componentname'] . ' - ' . $record['variant'] + ); + return $record['id']; + } + $this->importresults[] = get_string( + 'replacecompvariant', + 'tiny_elements', + $record['componentname'] . ' - ' . $record['variant'] + ); + return $current->id; + } + + /** + * Get import results. + * + * @return array + */ + public function get_importresults(): array { + return $this->importresults; + } + + /** + * Updates files metadata. + * + * @param string $categoryname The name of the category to update metadata for. + * @param int $draftitemid The draft item ID associated with the import process (default is 0). + * @return array Array of filemetadata objects. + */ + public function update_files_metadata($categoryname, $draftitemid = 0): array { + global $DB; + + $fs = get_file_storage(); + $xmlfile = $fs->get_file($this->contextid, 'tiny_elements', 'import', $draftitemid, '\/' . $categoryname . '\/', + constants::FILE_NAME_METADATA . '_' . $categoryname . '.xml'); + // Manage older exports without filemetadata. + if (!$xmlfile) { + return []; + } + + // Get xml content. + $xmlcontent = $xmlfile->get_content(); + try { + $xml = simplexml_load_string($xmlcontent); + } catch (\Exception $exception) { + $xml = false; + } + if (!$xml) { + return []; + } + + $data = []; + // Make data usable for further processing. + foreach ($xml as $catname => $rows) { + foreach ($rows as $row) { + $filemetadataobj = new \stdClass(); + foreach ($row as $column => $value) { + if ($column == 'license') { + $filemetadataobj->license = (string) $value->shortname; + } else { + $filemetadataobj->$column = (string) $value; + } + } + $data[$catname][] = $filemetadataobj; + } + } + return $data; + } +} diff --git a/classes/local/constants.php b/classes/local/constants.php new file mode 100644 index 0000000..1c23487 --- /dev/null +++ b/classes/local/constants.php @@ -0,0 +1,78 @@ +. + +namespace tiny_elements\local; + +/** + * Constants for tiny_elements + * + * @package tiny_elements + * @author Stefan Hanauska + * @copyright 2025 ISB Bayern + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class constants { + /** @var array All tables to export data from. */ + public const TABLES = [ + 'compcat' => 'tiny_elements_compcat', + 'component' => 'tiny_elements_component', + 'compflavor' => 'tiny_elements_comp_flavor', + 'compvariant' => 'tiny_elements_comp_variant', + 'flavor' => 'tiny_elements_flavor', + 'variant' => 'tiny_elements_variant', + ]; + /** @var array Table name aliases for compatibility with tiny_c4l exports. */ + public const TABLE_ALIASES = [ + 'tiny_elements_compcat' => 'tiny_c4l_compcat', + 'tiny_elements_component' => 'tiny_c4l_component', + 'tiny_elements_flavor' => 'tiny_c4l_flavor', + 'tiny_elements_variant' => 'tiny_c4l_variant', + 'tiny_elements_comp_flavor' => 'tiny_c4l_comp_flavor', + 'tiny_elements_comp_variant' => 'tiny_c4l_comp_variant', + ]; + /** @var array All tables that are optional. */ + public const OPTIONAL_TABLES = ['tiny_elements_comp_flavor', 'tiny_elements_comp_variant']; + + /** @var string Item. */ + public const ITEMNAME = 'row'; + + /** @var string CACHE_AREA the cache area for the tiny_elements plugin */ + public const CACHE_AREA = 'tiny_elements_css'; + + /** @var string JS_CACHE_KEY the cache key for the js code */ + public const JS_CACHE_KEY = 'tiny_elements_js'; + + /** @var string CSS_CACHE_KEY the cache key for the css code */ + public const CSS_CACHE_KEY = 'tiny_elements_css'; + + /** @var string CSS_CACHE_REV the cache key for the css revision */ + public const CSS_CACHE_REV = 'tiny_elements_cssrev'; + + /** @var string JS_CACHE_REV the cache key for the js revision */ + public const JS_CACHE_REV = 'tiny_elements_jsrev'; + + /** @var array FILE_OPTIONS the options for the filemanager */ + public const FILE_OPTIONS = ['subdirs' => 1, 'accepted_types' => ['web_image']]; + + /** @var array IMPORT_FILE_OPTIONS the options for the filemanager */ + public const IMPORT_FILE_OPTIONS = ['subdirs' => 0, 'accepted_types' => 'xml,zip', 'maxfiles' => 1]; + + /** @var string FILE_NAMES_EXPORT xml file_name for export*/ + public const FILE_NAME_EXPORT = 'tiny_elements_export.xml'; + + /** @var string FILE_NAME_METADATA xml file_name for metadata*/ + public const FILE_NAME_METADATA = 'tiny_elements_filemetadata'; +} diff --git a/classes/local/hook_callbacks.php b/classes/local/hook_callbacks.php new file mode 100644 index 0000000..2ba0173 --- /dev/null +++ b/classes/local/hook_callbacks.php @@ -0,0 +1,95 @@ +. + +namespace tiny_elements\local; + +use core\hook\output\before_http_headers; + +/** + * Class containing the hook callbacks for tiny_elements. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Hook callback function for the before_http_headers hook. + * + * Used to add our custom stylesheet and js to the DOM. + * + * @param before_http_headers $beforehttpheadershook + */ + public static function add_elements_data_to_dom(\core\hook\output\before_http_headers $beforehttpheadershook): void { + global $CFG; + + // Parameter to disable css delivery. + if (optional_param('tiny_elements_disable', false, PARAM_BOOL)) { + return; + } + // Don't run during initial install. + if (during_initial_install()) { + return; + } + // Only run if plugin is enabled. + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_enabled_plugins('tiny'); + if (!in_array('elements', $plugins)) { + return; + } + if ($CFG->forcelogin && !isloggedin()) { + return; + } + + // Don't run if we are in the management page. This should help to remove invalid css/js. + if ( + $beforehttpheadershook->renderer->get_page()->has_set_url() && + $beforehttpheadershook->renderer->get_page()->url->get_path() == '/lib/editor/tiny/plugins/elements/management.php' + ) { + return; + } + + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + $rev = $cache->get(constants::CSS_CACHE_REV); + if (!$rev) { + $rev = utils::rebuild_css_cache(); + } + $pluginfileurl = \moodle_url::make_pluginfile_url( + SYSCONTEXTID, + 'tiny_elements', + 'css', + null, + '/', + 'tiny_elements_styles.css?rev=' . $rev + ); + $beforehttpheadershook->renderer->get_page()->requires->css($pluginfileurl); + + $revjs = $cache->get(constants::JS_CACHE_REV); + if (!$revjs) { + $revjs = utils::rebuild_js_cache(); + } + $pluginfileurljs = \moodle_url::make_pluginfile_url( + SYSCONTEXTID, + 'tiny_elements', + 'js', + null, + '/', + 'tiny_elements_scripts.js?rev=' . $revjs + ); + $beforehttpheadershook->renderer->get_page()->requires->js($pluginfileurljs); + } +} diff --git a/classes/local/utils.php b/classes/local/utils.php new file mode 100644 index 0000000..fb0cd61 --- /dev/null +++ b/classes/local/utils.php @@ -0,0 +1,525 @@ +. + +namespace tiny_elements\local; + +use core\output\choicelist; + +/** + * Utility class for tiny_elements. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class utils { + /** + * Get all components. + * @param bool $isstudent + * @return array all components + */ + public static function get_all_components(bool $isstudent = false): array { + global $DB; + $conditions = []; + if ($isstudent) { + $conditions['hideforstudents'] = 0; + } + $componentrecords = $DB->get_records('tiny_elements_component', $conditions, 'displayorder'); + $components = []; + foreach ($componentrecords as $record) { + $components[] = [ + 'id' => $record->id, + 'name' => $record->name, + 'displayname' => $record->displayname, + 'categoryname' => $record->categoryname, + 'code' => self::replace_pluginfile_urls($record->code, true), + 'text' => $record->text, + 'displayorder' => $record->displayorder, + 'js' => self::replace_pluginfile_urls($record->js ?? '', true), + ]; + } + return $components; + } + + /** + * Get all variants. + * @param string $categoryname + * @param string $query + * @return array all variants + */ + public static function get_all_variants(string $categoryname = '', string $query = ''): array { + global $DB; + $where = ''; + $params = []; + if (!empty($categoryname)) { + if (!empty((int)$categoryname)) { + $categoryname = $DB->get_field('tiny_elements_compcat', 'name', ['id' => $categoryname]); + } + $where .= 'categoryname = :categoryname'; + $params['categoryname'] = $categoryname; + } + if (!empty($query)) { + $sql = $DB->sql_like('name', ':query', false, false); + if (!empty($where)) { + $where .= ' AND '; + } + $where .= $sql; + $params['query'] = '%' . $DB->sql_like_escape($query) . '%'; + } + $variants = $DB->get_records_sql( + "SELECT * FROM {tiny_elements_variant}" . (!empty($where) ? " WHERE " . $where : ""), + $params + ); + foreach ($variants as $variant) { + $variant->content = self::replace_pluginfile_urls($variant->content, true); + } + return $variants; + } + + /** + * Get all component variants. + * + * @return array all component variants + */ + public static function get_all_comp_variants(): array { + global $DB; + $compvariants = $DB->get_records('tiny_elements_comp_variant', null, ''); + // Sort all variants to the component. key: component name, value: array of variant names. + $components = []; + foreach ($compvariants as $compvariant) { + $components[$compvariant->componentname] = array_merge( + [$compvariant->variant], + $components[$compvariant->componentname] ?? [] + ); + } + return $components; + } + + /** + * Get all component categories. + * @return array all component categories + */ + public static function get_all_compcats(): array { + global $DB; + $categories = $DB->get_records('tiny_elements_compcat', null, 'displayorder'); + return array_values($categories); + } + + /** + * Get all component flavors. + * @return array all component flavors + */ + public static function get_all_comp_flavors(): array { + global $DB; + $compflavors = $DB->get_records('tiny_elements_comp_flavor', [], ''); + $components = []; + foreach ($compflavors as $compflavor) { + $components[$compflavor->componentname] = array_merge( + [$compflavor->flavorname], + $components[$compflavor->componentname] ?? [] + ); + } + return $components; + } + + /** + * Get all flavors. + * @param bool $isstudent + * @param string $categoryname + * @param string $query + * @return array all flavors + */ + public static function get_all_flavors(bool $isstudent = false, string $categoryname = '', string $query = ''): array { + global $DB; + $where = ''; + $params = []; + if ($isstudent) { + $where .= 'hideforstudents = 0'; + } + if (!empty($categoryname)) { + if (!empty((int)$categoryname)) { + $categoryname = $DB->get_field('tiny_elements_compcat', 'name', ['id' => $categoryname]); + } + if (!empty($where)) { + $where .= ' AND '; + } + $where .= 'categoryname = :categoryname'; + $params['categoryname'] = $categoryname; + } + if (!empty($query)) { + $sql = $DB->sql_like('name', ':query', false, false); + if (!empty($where)) { + $where .= ' AND '; + } + $where .= $sql; + $params['query'] = '%' . $DB->sql_like_escape($query) . '%'; + } + $flavors = $DB->get_records_sql( + " + SELECT * + FROM {tiny_elements_flavor}" . + (!empty($where) ? " WHERE " . $where : "") . + " ORDER BY displayorder", + $params + ); + + $flavorsbyname = []; + foreach ($flavors as $flavor) { + $flavorsbyname[$flavor->name] = $flavor; + $flavorsbyname[$flavor->name]->categories = []; + $flavorsbyname[$flavor->name]->content = self::replace_pluginfile_urls($flavor->content ?? '', true); + } + return $flavorsbyname; + } + + /** + * Get all data for the elements editor. + * @param bool $isstudent + * @return array all data for the elements editor + */ + public static function get_elements_data(bool $isstudent = false): array { + $components = self::get_all_components($isstudent); + $compcats = self::get_all_compcats(); + $flavors = self::get_all_flavors($isstudent); + $variants = self::get_all_variants(); + $componentflavors = self::get_all_comp_flavors(); + $componentvariants = self::get_all_comp_variants(); + + foreach ($components as $key => $component) { + // Add flavors to components structure. + $components[$key]['flavors'] = $componentflavors[$component['name']] ?? []; + // Add categories to flavors. + foreach ($components[$key]['flavors'] as $flavor) { + if (!isset($flavors[$flavor])) { + continue; + } + $flavors[$flavor]->categories[] = $component['categoryname']; + } + + // Add variants to components structure. + $components[$key]['variants'] = $componentvariants[$component['name']] ?? []; + } + + foreach ($flavors as $flavor) { + $flavor->categories = join(',', array_unique($flavor->categories)); + } + + return [ + 'components' => $components, + 'categories' => $compcats, + 'flavors' => $flavors, + 'variants' => $variants, + ]; + } + + /** + * Rebuild the css cache. + * + * @return int the new revision for the cache + */ + public static function rebuild_css_cache(): int { + global $DB; + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + $iconcssentries = []; + $componentcssentries = []; + $variantscssentries = []; + $components = []; + $variants = []; + try { + $components = $DB->get_records('tiny_elements_component', null, '', 'id, name, css, iconurl'); + $categorycssentries = $DB->get_fieldset('tiny_elements_compcat', 'css'); + $flavors = $DB->get_records('tiny_elements_flavor', null, 'id, name'); + $flavorcssentries = $DB->get_fieldset('tiny_elements_flavor', 'css'); + $variants = $DB->get_records('tiny_elements_variant', null, '', 'name, iconurl, css'); + } catch (\dml_exception $e) { + // This is done to prevent the plugin from crashing the whole site if the database tables + // are not yet installed for some reason. + return 0; + } + foreach ($variants as $variant) { + $variantscssentries[] = $variant->css; + if (empty($variant->iconurl)) { + continue; + } + $iconcssentries[] = self::variant_icon_css($variant->name, self::replace_pluginfile_urls($variant->iconurl, true)); + } + $componentflavors = $DB->get_records('tiny_elements_comp_flavor'); + foreach ($componentflavors as $componentflavor) { + if (empty($componentflavor->iconurl)) { + continue; + } + $iconcssentries[] = self::button_icon_css( + $componentflavor->componentname, + self::replace_pluginfile_urls($componentflavor->iconurl, true), + $componentflavor->flavorname + ); + } + foreach ($components as $component) { + $componentcssentries[] = $component->css; + if (empty($component->iconurl)) { + continue; + } + $iconcssentries[] = self::button_icon_css($component->name, self::replace_pluginfile_urls($component->iconurl, true)); + } + $cssentries = array_merge( + $categorycssentries, + $componentcssentries, + $flavorcssentries, + $variantscssentries, + $iconcssentries, + ); + $css = array_reduce( + $cssentries, + fn($current, $add) => $current . PHP_EOL . $add, + '/* This file contains the stylesheet for the tiny_elements plugin.*/' + ); + $css = self::replace_pluginfile_urls($css, true); + $css = \core_minify::css($css); + if (empty($css)) { + $css = ' '; + } + $clock = \core\di::get(\core\clock::class); + $rev = $clock->time(); + $cache->set(constants::CSS_CACHE_KEY, $css); + $cache->set(constants::CSS_CACHE_REV, $rev); + return $rev; + } + + /** + * Rebuild the js cache. + * @return int the new revision for the cache + */ + public static function rebuild_js_cache(): int { + global $DB; + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + $jsentries = []; + try { + $jsentries = $DB->get_records_menu('tiny_elements_component', null, '', 'id, js'); + } catch (\dml_exception $e) { + // This is done to prevent the plugin from crashing the whole site if the database tables + // are not yet installed for some reason. + return 0; + } + $js = array_reduce( + $jsentries, + fn($current, $add) => $current . PHP_EOL . $add, + '/* This file contains the javascript for the tiny_elements plugin.*/' + ); + $js = self::replace_pluginfile_urls($js, true); + $js = \core_minify::js($js); + if (empty($js)) { + $js = ' '; + } + $cache->set(constants::JS_CACHE_KEY, $js); + $clock = \core\di::get(\core\clock::class); + $rev = $clock->time(); + $cache->set(constants::JS_CACHE_REV, $rev); + return $rev; + } + + /** + * Purge the tiny_elements css cache. + */ + public static function purge_css_cache(): void { + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + $cache->delete(constants::CSS_CACHE_KEY); + $cache->delete(constants::CSS_CACHE_REV); + } + + /** + * Purge the tiny_elements js cache. + */ + public static function purge_js_cache(): void { + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + $cache->delete(constants::JS_CACHE_KEY); + $cache->delete(constants::JS_CACHE_REV); + } + + /** + * Purge and rebuild all caches. + */ + public static function purge_and_rebuild_caches(): void { + self::purge_css_cache(); + self::purge_js_cache(); + self::rebuild_css_cache(); + self::rebuild_js_cache(); + } + + /** + * Helper function to retrieve the currently cached tiny_elements css. + * + * @return string|false the css code as string, false if no cache entry found + */ + public static function get_css_from_cache(): string|false { + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + return $cache->get(constants::CSS_CACHE_KEY); + } + + /** + * Helper function to retrieve the currently cached tiny_elements js. + * + * @return string|false the js code as string, false if no cache entry found + */ + public static function get_js_from_cache(): string|false { + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + return $cache->get(constants::JS_CACHE_KEY); + } + + /** + * Replace @@PLUGINFILE@@ with the correct URL and vice versa. + * + * @param string $content the content to replace the URL in + * @param bool $realurl if true, get the real URL, otherwise replace it + */ + public static function replace_pluginfile_urls(string $content, bool $realurl = false): string { + global $CFG; + if (!$realurl) { + $content = str_replace($CFG->wwwroot . '/pluginfile.php', '@@PLUGINFILE@@', $content); + } else { + $content = str_replace('@@PLUGINFILE@@', $CFG->wwwroot . '/pluginfile.php', $content); + } + return $content; + } + + /** + * Get the css for a button with an icon. + * + * @param string $variant + * @param string $iconurl + * @return string + */ + public static function variant_icon_css(string $variant, string $iconurl): string { + return << $newid) { + $subject = self::update_pluginfile_tags($oldid, $newid, $subject, 'bulk'); + } + $subject = self::remove_mark($subject, 'bulk'); + return $subject; + } + + /** + * Rename the pluginfile tags from tiny_c4l to tiny_elements. + * + * @param string $subject + * @return string + */ + public static function update_c4l_pluginfile_tags(string $subject): string { + $oldstring = '@@PLUGINFILE@@/1/tiny_c4l/'; + $newstring = '@@PLUGINFILE@@/1/tiny_elements/'; + return str_replace($oldstring, $newstring, $subject); + } + + /** + * Update the pluginfile tags in the given subject. + * + * @param int $oldid + * @param int $newid + * @param string $subject + * @param string $mark (optional) A string to mark the path - to be removed later. + * @return string + */ + public static function update_pluginfile_tags(int $oldid, int $newid, string $subject, string $mark = ''): string { + $oldstring = '@@PLUGINFILE@@/1/tiny_elements/images/' . $oldid . '/'; + $newstring = '@@PLUGINFILE@@/1/tiny_elements/' . $mark . 'images/' . $newid . '/'; + return str_replace($oldstring, $newstring, $subject); + } + + /** + * Remove the mark from the given subject. + * + * @param string $subject + * @param string $mark + * @return string + */ + public static function remove_mark(string $subject, string $mark): string { + $newstring = '@@PLUGINFILE@@/1/tiny_elements/images/'; + $oldstring = '@@PLUGINFILE@@/1/tiny_elements/' . $mark . 'images/'; + return str_replace($oldstring, $newstring, $subject); + } + + /** + * Return a list of all images in the given context. + * + * @param int $contextid + * @param int $categoryid + * @param string $categoryname + * @return array + */ + public static function get_all_images(int $contextid = SYSCONTEXTID, int $categoryid = 0, string $categoryname = ''): array { + global $DB; + if (empty($categoryid) && !empty($categoryname)) { + $categoryid = $DB->get_field('tiny_elements_compcat', 'id', ['name' => $categoryname]); + } + $fs = get_file_storage(); + $files = $fs->get_area_files($contextid, 'tiny_elements', 'images', (empty($categoryid) ? false : $categoryid)); + $processedfiles = []; + foreach ($files as $file) { + if ($file->is_directory()) { + continue; + } + + $processedfiles[] = [ + 'url' => \moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename() + )->out(), + 'name' => $file->get_filepath() . $file->get_filename(), + ]; + } + return $processedfiles; + } +} diff --git a/classes/manager.php b/classes/manager.php new file mode 100644 index 0000000..f6fe110 --- /dev/null +++ b/classes/manager.php @@ -0,0 +1,599 @@ +. + +namespace tiny_elements; + +use tiny_elements\local\constants; + +/** + * Class manager + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class manager { + /** @var int $contextid */ + protected int $contextid = 1; + + /** + * Constructor. + * + * @param int $contextid + */ + public function __construct(int $contextid = SYSCONTEXTID) { + $this->contextid = $contextid; + } + + /** + * Get the context id. + * + * @return int + */ + public function get_contextid(): int { + return $this->contextid; + } + + /** + * Delete a category. + * + * @param int $id + */ + public function delete_compcat(int $id): void { + global $DB; + $fs = get_file_storage(); + $categoryname = $DB->get_field('tiny_elements_compcat', 'name', ['id' => $id]); + $fs->delete_area_files($this->contextid, 'tiny_elements', 'images', $id); + $DB->delete_records('tiny_elements_compcat', ['id' => $id]); + + foreach ($DB->get_records('tiny_elements_component', ['categoryname' => $categoryname]) as $component) { + $this->delete_component($component->id); + } + + foreach ($DB->get_records('tiny_elements_flavor', ['categoryname' => $categoryname]) as $flavor) { + $this->delete_flavor($flavor->id); + } + + foreach ($DB->get_records('tiny_elements_variant', ['categoryname' => $categoryname]) as $variant) { + $this->delete_variant($variant->id); + } + + // Purge CSS and JS cache. + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + \tiny_elements\local\utils::purge_js_cache(); + \tiny_elements\local\utils::rebuild_js_cache(); + } + + /** + * Delete a flavor. + * + * @param int $id + */ + public function delete_flavor(int $id): void { + global $DB; + $sql = "DELETE FROM {tiny_elements_comp_flavor} + WHERE flavorname IN ( + SELECT name FROM {tiny_elements_flavor} + WHERE id = ? + )"; + $DB->execute($sql, [$id]); + $DB->delete_records('tiny_elements_flavor', ['id' => $id]); + + // Purge CSS cache. + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + /** + * Delete a variant. + * + * @param int $id + * @return void + */ + public function delete_variant(int $id): void { + global $DB; + $sql = "DELETE FROM {tiny_elements_comp_variant} + WHERE variant IN ( + SELECT name FROM {tiny_elements_variant} + WHERE id = ? + )"; + $DB->execute($sql, [$id]); + $DB->delete_records('tiny_elements_variant', ['id' => $id]); + + // Purge CSS cache. + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + /** + * Delete a component. + * + * @param int $id + * @return void + */ + public function delete_component(int $id): void { + global $DB; + $componentname = $DB->get_field('tiny_elements_component', 'name', ['id' => $id]); + $DB->delete_records('tiny_elements_comp_flavor', ['componentname' => $componentname]); + $DB->delete_records('tiny_elements_comp_variant', ['componentname' => $componentname]); + $DB->delete_records('tiny_elements_component', ['id' => $id]); + + // Purge CSS and JS cache. + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + \tiny_elements\local\utils::purge_js_cache(); + \tiny_elements\local\utils::rebuild_js_cache(); + } + + /** + * Duplicate a category. + * + * @param int $id + */ + public function duplicate_compcat(int $id): void { + global $DB; + $compcat = $DB->get_record('tiny_elements_compcat', ['id' => $id]); + if ($compcat) { + $newcompcat = clone $compcat; + unset($newcompcat->id); + $newcompcat->displayname = get_string('copyof', 'tiny_elements', $newcompcat->displayname); + $newcompcat->name .= time(); + $newcompcat->id = $DB->insert_record('tiny_elements_compcat', $newcompcat); + $fs = get_file_storage(); + $files = $fs->get_area_files($this->contextid, 'tiny_elements', 'images', $id); + foreach ($files as $file) { + $fs->create_file_from_storedfile([ + 'contextid' => $this->contextid, + 'component' => 'tiny_elements', + 'filearea' => 'images', + 'itemid' => $newcompcat->id, + 'filepath' => $file->get_filepath(), + 'filename' => $file->get_filename(), + ], $file); + } + } + } + + /** + * Duplicate a flavor. + * + * @param int $id + */ + public function duplicate_flavor(int $id): void { + global $DB; + $flavor = $DB->get_record('tiny_elements_flavor', ['id' => $id]); + if ($flavor) { + $newflavor = clone $flavor; + unset($newflavor->id); + $newflavor->displayname = get_string('copyof', 'tiny_elements', $newflavor->displayname); + $newflavor->name .= time(); + $newflavor->id = $DB->insert_record('tiny_elements_flavor', $newflavor); + } + } + + /** + * Duplicate a variant. + * + * @param int $id + */ + public function duplicate_variant(int $id): void { + global $DB; + $variant = $DB->get_record('tiny_elements_variant', ['id' => $id]); + if ($variant) { + $newvariant = clone $variant; + unset($newvariant->id); + $newvariant->displayname = get_string('copyof', 'tiny_elements', $newvariant->displayname); + $newvariant->name .= time(); + $newvariant->id = $DB->insert_record('tiny_elements_variant', $newvariant); + } + } + + /** + * Duplicate a component. + * + * @param int $id + */ + public function duplicate_component(int $id): void { + global $DB; + $component = $DB->get_record('tiny_elements_component', ['id' => $id]); + if ($component) { + $newcomponent = clone $component; + unset($newcomponent->id); + $newcomponent->displayname = get_string('copyof', 'tiny_elements', $newcomponent->displayname); + $newcomponent->name .= time(); + $newcomponent->id = $DB->insert_record('tiny_elements_component', $newcomponent); + } + } + + /** + * Add a category. + * + * @param object $data + * @return int id of the new category + */ + public function add_compcat(object $data) { + global $DB; + $data->timecreated = time(); + $data->timemodified = time(); + $recordid = $DB->insert_record(constants::TABLES['compcat'], $data); + file_save_draft_area_files( + $data->compcatfiles, + $this->contextid, + 'tiny_elements', + 'images', + $recordid, + constants::FILE_OPTIONS + ); + + // Purge CSS cache if necessary. + if (!empty($data->css)) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + return $recordid; + } + + /** + * Add a flavor. + * + * @param object $data + * @return int id of the new flavor + */ + public function add_flavor(object $data) { + global $DB; + $data->timecreated = time(); + $data->timemodified = time(); + + $result = $DB->insert_record(constants::TABLES['flavor'], $data); + + // Purge CSS cache if necessary. + if (!empty($data->css)) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + return $result; + } + + /** + * Add a variant. + * + * @param object $data + * @return int id of the new variant + */ + public function add_variant(object $data) { + global $DB; + $data->timecreated = time(); + $data->timemodified = time(); + + $result = $DB->insert_record(constants::TABLES['variant'], $data); + + // Purge CSS cache if necessary. + if (!empty($data->css) || !empty($data->iconurl)) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + return $result; + } + + /** + * Add a component. + * + * @param object $data + * @return int id of the new component + */ + public function add_component(object $data) { + global $DB; + $data->timecreated = time(); + $data->timemodified = time(); + + $data->id = $DB->insert_record(constants::TABLES['component'], $data); + + if (count($data->flavors) > 0) { + foreach ($data->flavors as $flavor) { + $DB->insert_record('tiny_elements_comp_flavor', [ + 'componentname' => $data->name, + 'flavorname' => $flavor, + ]); + } + } + + if (count($data->variants) > 0) { + foreach ($data->variants as $variant) { + $DB->insert_record('tiny_elements_comp_variant', [ + 'componentname' => $data->name, + 'variant' => $variant, + ]); + } + } + + // Purge CSS cache if necessary. + if (!empty($data->css) || !empty($data->iconurl)) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + // Purge JS cache if necessary. + if (!empty($data->js)) { + \tiny_elements\local\utils::purge_js_cache(); + \tiny_elements\local\utils::rebuild_js_cache(); + } + + return $data->id; + } + + /** + * Update a category. + * + * @param object $data + * @return bool + */ + public function update_compcat(object $data): bool { + global $DB; + $data->timemodified = time(); + $oldrecord = $DB->get_record(constants::TABLES['compcat'], ['id' => $data->id]); + file_save_draft_area_files( + $data->compcatfiles, + $this->contextid, + 'tiny_elements', + 'images', + $data->id, + constants::FILE_OPTIONS + ); + + $result = $DB->update_record(constants::TABLES['compcat'], $data); + + $result &= $DB->execute( + "UPDATE {tiny_elements_component} + SET categoryname = ? + WHERE categoryname = ?", + [$data->name, $oldrecord->name] + ); + + $result &= $DB->execute( + "UPDATE {tiny_elements_flavor} + SET categoryname = ? + WHERE categoryname = ?", + [$data->name, $oldrecord->name] + ); + + $result &= $DB->execute( + "UPDATE {tiny_elements_variant} + SET categoryname = ? + WHERE categoryname = ?", + [$data->name, $oldrecord->name] + ); + + // Purge CSS cache if necessary. + if ($data->css != $oldrecord->css) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + return $result; + } + + /** + * Update a flavor. + * + * @param object $data + * @return bool + */ + public function update_flavor(object $data): bool { + global $DB; + $data->timemodified = time(); + $data->hideforstudents = !empty($data->hideforstudents); + + $oldrecord = $DB->get_record(constants::TABLES['flavor'], ['id' => $data->id]); + + $result = $DB->update_record(constants::TABLES['flavor'], $data); + + if ($oldrecord->name != $data->name) { + $result &= $DB->execute( + "UPDATE {tiny_elements_comp_flavor} + SET flavorname = ? + WHERE flavorname = ?", + [$data->name, $oldrecord->name] + ); + } + + // Purge CSS cache if necessary. + if ($data->css != $oldrecord->css) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + return $result; + } + + /** + * Update a variant. + * + * @param object $data + * @return bool + */ + public function update_variant(object $data): bool { + global $DB; + $data->timemodified = time(); + + $oldrecord = $DB->get_record(constants::TABLES['variant'], ['id' => $data->id]); + + $result = $DB->update_record(constants::TABLES['variant'], $data); + + if ($oldrecord->name != $data->name) { + $result &= $DB->execute( + "UPDATE {tiny_elements_comp_variant} + SET variant = ? + WHERE variant = ?", + [$data->name, $oldrecord->name] + ); + } + + // Purge CSS cache if necessary. + if ($data->css != $oldrecord->css || $data->iconurl != $oldrecord->iconurl) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + return $result; + } + + /** + * Update a component. + * + * @param object $data + * @return bool + */ + public function update_component(object $data): bool { + global $DB; + $data->timemodified = time(); + $data->hideforstudents = !empty($data->hideforstudents); + $oldrecord = $DB->get_record(constants::TABLES['component'], ['id' => $data->id]); + $result = $DB->update_record(constants::TABLES['component'], $data); + // Update component flavors, keep existing iconurls. + if ($oldrecord) { + $records = $DB->get_records( + 'tiny_elements_comp_flavor', + ['componentname' => $oldrecord->name], + '', + 'flavorname, iconurl' + ); + $DB->delete_records('tiny_elements_comp_flavor', ['componentname' => $oldrecord->name]); + } + if (count($data->flavors) > 0) { + foreach ($data->flavors as $flavor) { + $DB->insert_record('tiny_elements_comp_flavor', [ + 'componentname' => $data->name, + 'flavorname' => $flavor, + 'iconurl' => $records[$flavor]->iconurl ?? '', + ]); + } + } + // Update component variants. + if ($oldrecord) { + $records = $DB->get_records('tiny_elements_comp_variant', ['componentname' => $oldrecord->name]); + $DB->delete_records('tiny_elements_comp_variant', ['componentname' => $oldrecord->name]); + } + if (count($data->variants) > 0) { + foreach ($data->variants as $variant) { + $DB->insert_record('tiny_elements_comp_variant', [ + 'componentname' => $data->name, + 'variant' => $variant, + ]); + } + } + + // Purge CSS cache if necessary. + if ($data->css != $oldrecord->css) { + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + } + + // Purge JS cache if necessary. + if (($oldrecord->js != $data->js)) { + \tiny_elements\local\utils::purge_js_cache(); + \tiny_elements\local\utils::rebuild_js_cache(); + } + + return $result; + } + + /** + * Get the name of the category for a variant. If there are multiple categories where the variant is used, + * get the one with the most components. + * + * @param string $variantname + * @return string + */ + public function get_compcatname_for_variant(string $variantname): string { + global $DB; + $compcatname = ''; + $compcat = $DB->get_record_sql( + "SELECT c.name, COUNT(c.id) AS cnt + FROM {tiny_elements_compcat} c + JOIN {tiny_elements_component} cp + ON c.name = cp.categoryname + JOIN {tiny_elements_comp_variant} cpv + ON cp.name = cpv.componentname + WHERE cpv.variant = ? + GROUP BY c.name + ORDER BY cnt DESC", + [$variantname], + IGNORE_MULTIPLE + ); + if ($compcat) { + $compcatname = $compcat->name; + } + return $compcatname ?? ''; + } + + /** + * Get the name of the category for a flavor. If there are multiple categories where the flavor is used, + * get the one with the most components. + * + * @param string $flavorname + * @return string + */ + public function get_compcatname_for_flavor(string $flavorname): string { + global $DB; + $compcatname = ''; + $compcat = $DB->get_record_sql( + "SELECT c.name, COUNT(c.id) AS cnt + FROM {tiny_elements_compcat} c + JOIN {tiny_elements_component} cp + ON c.name = cp.categoryname + JOIN {tiny_elements_comp_flavor} cpf + ON cp.name = cpf.componentname + WHERE cpf.flavorname = ? + GROUP BY c.name + ORDER BY cnt DESC", + [$flavorname], + IGNORE_MULTIPLE + ); + if ($compcat) { + $compcatname = $compcat->name; + } + return $compcatname ?? ''; + } + + /** + * This function deletes all data from the plugin tables and from the filesystem. + * Use with caution! + */ + public function wipe(): void { + global $DB; + $DB->delete_records(constants::TABLES['compcat']); + $DB->delete_records(constants::TABLES['component']); + $DB->delete_records(constants::TABLES['flavor']); + $DB->delete_records(constants::TABLES['variant']); + $DB->delete_records(constants::TABLES['compflavor']); + $DB->delete_records(constants::TABLES['compvariant']); + + $fs = get_file_storage(); + $fs->delete_area_files($this->contextid, 'tiny_elements', 'images'); + $fs->delete_area_files($this->contextid, 'tiny_elements', 'export'); + + // Purge CSS and JS cache. + \tiny_elements\local\utils::purge_css_cache(); + \tiny_elements\local\utils::rebuild_css_cache(); + \tiny_elements\local\utils::purge_js_cache(); + \tiny_elements\local\utils::rebuild_js_cache(); + } +} diff --git a/classes/plugininfo.php b/classes/plugininfo.php index 17862a9..56d9cc0 100644 --- a/classes/plugininfo.php +++ b/classes/plugininfo.php @@ -14,26 +14,24 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tiny_c4l; +namespace tiny_elements; use context; use editor_tiny\plugin; use editor_tiny\plugin_with_buttons; use editor_tiny\plugin_with_configuration; use editor_tiny\plugin_with_menuitems; +use tiny_elements\local\utils; +use tiny_elements\local\constants; /** - * Tiny c4l plugin for Moodle. + * Tiny elements plugin for Moodle. * - * @package tiny_c4l + * @package tiny_elements * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class plugininfo extends plugin implements - plugin_with_buttons, - plugin_with_menuitems, - plugin_with_configuration { - +class plugininfo extends plugin implements plugin_with_buttons, plugin_with_configuration, plugin_with_menuitems { /** * Get the editor buttons for this plugins * @@ -41,7 +39,7 @@ class plugininfo extends plugin implements */ public static function get_available_buttons(): array { return [ - 'tiny_c4l/c4l', + 'tiny_elements/elements', ]; } /** @@ -51,7 +49,7 @@ public static function get_available_buttons(): array { */ public static function get_available_menuitems(): array { return [ - 'tiny_c4l/c4l', + 'tiny_elements/elements', ]; } @@ -63,7 +61,6 @@ public static function get_available_menuitems(): array { * @param array $options * @param array $fpoptions * @param \editor_tiny\editor|null $editor - * @return void * * @return array */ @@ -73,126 +70,50 @@ public static function get_plugin_configuration_for_context( array $fpoptions, ?\editor_tiny\editor $editor = null ): array { - - $config = get_config('tiny_c4l'); - $viewc4l = has_capability('tiny/c4l:viewplugin', $context); - $showpreview = get_config('tiny_c4l', 'enablepreview'); - $isstudent = !has_capability('gradereport/grader:view', $context); - $allowedcomps = []; - if ($isstudent) { - $aimedcomps = explode(',', get_config('tiny_c4l', 'aimedatstudents')); - $notintendedcomps = explode(',', get_config('tiny_c4l', 'notintendedforstudents')); - $allowedcomps = array_merge($aimedcomps, $notintendedcomps); + $viewelements = has_capability('tiny/elements:viewplugin', $context); + $showpreview = get_config('tiny_elements', 'enablepreview'); + $isstudent = !has_capability('tiny/elements:showteachercomponents', $context); + $canmanage = has_capability('tiny/elements:manage', $context); + + $cache = \cache::make('tiny_elements', constants::CACHE_AREA); + $rev = $cache->get(constants::CSS_CACHE_REV); + if (!$rev) { + $rev = utils::rebuild_css_cache(); } - - // Get CSS preview. - $previewcss = $config->custompreviewcss ?? ''; - - // Get custom components. - $customcomponents = self::get_custom_components($config); + $cssurl = \moodle_url::make_pluginfile_url( + SYSCONTEXTID, + 'tiny_elements', + '', + null, + '', + 'tiny_elements_styles.css?rev=' . $rev + )->out(); return [ 'isstudent' => $isstudent, - 'allowedcomps' => $allowedcomps, 'showpreview' => ($showpreview == '1'), - 'viewc4l' => $viewc4l, - 'previewcss' => $previewcss, - 'customcomps' => $customcomponents, + 'viewelements' => $viewelements, + 'cssurl' => $cssurl, + 'canmanage' => $canmanage, ]; } /** - * Get the custom components. + * Check if the plugin is enabled for the context * - * @param stdClass $config tiny_c4l config - * @return array + * @param context $context + * @param array $options + * @param array $fpoptions + * @param \editor_tiny\editor|null $editor + * + * @return bool */ - public static function get_custom_components(\stdClass $config) { - global $OUTPUT; - - $customcomponents = []; - if ($config->customcompcount > 0) { - $context = \context_system::instance(); - $customfiles = []; - if ($config->customimagesbank) { - // Get filearea. - $fs = get_file_storage(); - - // Get all files from filearea. - $files = $fs->get_area_files($context->id, 'tiny_c4l', 'customimagesbank', - false, 'itemid', false); - foreach ($files as $file) { - $customfiles[$file->get_filename()] = $file; - } - } - for ($i = 1; $i <= $config->customcompcount; $i++) { - $compcode = "customcompcode{$i}"; - $compenable = "customcompenable{$i}"; - $compname = "customcompname{$i}"; - if ($config->$compenable === '1' - && !empty(trim($config->$compname)) - && !empty(trim($config->$compcode))) { - - // Component parameters. - $compicon = "customcompicon{$i}"; - $comptext = "customcomptext{$i}"; - $compvar = "customcompvariant{$i}"; - $compsort = "customcompsortorder{$i}"; - - if (!empty($config->$compicon)) { - $icon = \moodle_url::make_pluginfile_url($context->id, 'tiny_c4l', - "customcompicon{$i}", 0, '/', basename($config->$compicon)); - } else { - $icon = $OUTPUT->image_url('c4l_customcomponent_icon', 'tiny_c4l'); - } - - // Replace {} before searching for images and cleaning code (FORMAT_HTML). - $html = str_replace('{{CUSTOMCLASS}}', '~~CUSTOMCLASS~~', $config->$compcode); - $html = str_replace('{{PLACEHOLDER}}', '~~PLACEHOLDER~~', $html); - - // Set url images. - $html = preg_replace_callback('/{{([^}]*)}}/', - function ($matches) use ($customfiles) { - if (isset($matches[1]) && isset($customfiles[$matches[1]])) { - $file = $customfiles[$matches[1]]; - $fileurl = \moodle_url::make_pluginfile_url($file->get_contextid(), $file->get_component(), - $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), - $file->get_filename(), false)->out(); - - return $fileurl; - } else { - return ''; - } - }, - $html - ); - - // Clean HTML code. - $html = format_text($html, FORMAT_HTML); - $html = preg_replace('/ style=("|\')(.*?)("|\')/', '', $html); - - // Restore {}. - $html = str_replace('~~CUSTOMCLASS~~', '{{CUSTOMCLASS}}', $html); - $html = str_replace('~~PLACEHOLDER~~', '{{PLACEHOLDER}}', $html); - - $key = count($customcomponents); - $customcomponents[$key]['id'] = $i; - $customcomponents[$key]['name'] = 'customcomp' . $i; - $customcomponents[$key]['buttonname'] = $config->$compname; - $customcomponents[$key]['icon'] = $icon->out(); - $customcomponents[$key]['code'] = $html; - $customcomponents[$key]['text'] = $config->$comptext ?? ''; - $customcomponents[$key]['variants'] = $config->$compvar === '1'; - $customcomponents[$key]['sortorder'] = $config->$compsort; - } - } - - // Sort components. - usort($customcomponents, function($a, $b) { - return $a['sortorder'] <=> $b['sortorder']; - }); - } - - return $customcomponents; + public static function is_enabled( + context $context, + array $options, + array $fpoptions, + ?\editor_tiny\editor $editor = null + ): bool { + return has_capability('tiny/elements:viewplugin', $context); } } diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index b4f91a6..dd9d236 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -14,33 +14,32 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -namespace tiny_c4l\privacy; - -defined('MOODLE_INTERNAL') || die(); +namespace tiny_elements\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\writer; /** - * Privacy API implementation for the Components for Learning (C4L) plugin. + * Privacy API implementation for the Components for Learning (Elements) plugin. * - * @package tiny_c4l + * @package tiny_elements * @category privacy * @copyright 2023 Marc Català * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements - \core_privacy\local\metadata\provider, - \core_privacy\local\request\user_preference_provider { - + \core_privacy\local\metadata\provider, + \core_privacy\local\request\user_preference_provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ - public static function get_metadata(collection $collection) : collection { - $collection->add_user_preference('c4l_components_variants', 'privacy:preference:components_variants'); + public static function get_metadata(collection $collection): collection { + $collection->add_user_preference('elements_category', 'privacy:preference:category'); + $collection->add_user_preference('elements_components_variants', 'privacy:preference:components_variants'); + $collection->add_user_preference('elements_components_flavors', 'privacy:preference:components_flavors'); return $collection; } @@ -53,10 +52,34 @@ public static function get_metadata(collection $collection) : collection { public static function export_user_preferences(int $userid) { // Variants. - $variants = get_user_preferences('c4l_components_variants', null, $userid); + $variants = get_user_preferences('elements_components_variants', null, $userid); if ($variants !== null) { - writer::export_user_preference('tiny_c4l', 'c4l_components_variants', $variants, - get_string('privacy:preference:components_variants', 'tiny_c4l')); + writer::export_user_preference( + 'tiny_elements', + 'elements_components_variants', + $variants, + get_string('privacy:preference:components_variants', 'tiny_elements') + ); + } + + $flavors = get_user_preferences('elements_components_flavors', null, $userid); + if ($flavors !== null) { + writer::export_user_preference( + 'tiny_elements', + 'elements_components_flavors', + $flavors, + get_string('privacy:preference:components_flavors', 'tiny_elements') + ); + } + + $category = get_user_preferences('elements_category', null, $userid); + if ($category !== null) { + writer::export_user_preference( + 'tiny_elements', + 'elements_category', + $category, + get_string('privacy:preference:category', 'tiny_elements') + ); } } } diff --git a/cli/installbase.php b/cli/installbase.php new file mode 100644 index 0000000..023539f --- /dev/null +++ b/cli/installbase.php @@ -0,0 +1,33 @@ +. + +/** + * Manually install templates from base.zip. + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define('CLI_SCRIPT', true); + +require(__DIR__ . '/../../../../../../config.php'); + +set_debugging(DEBUG_ALL); + +$basezip = __DIR__ . '/../db/base.zip'; +$importer = new \tiny_elements\importer(); +$importer->import($basezip); diff --git a/db/access.php b/db/access.php index 6d53677..3d9c5f7 100644 --- a/db/access.php +++ b/db/access.php @@ -15,9 +15,9 @@ // along with Moodle. If not, see . /** - * Capabilities for the tiny_c4l plugin. + * Capabilities for the tiny_elements plugin. * - * @package tiny_c4l + * @package tiny_elements * @copyright 2022 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $capabilities = [ - 'tiny/c4l:viewplugin' => [ + 'tiny/elements:viewplugin' => [ 'captype' => 'write', 'contextlevel' => CONTEXT_MODULE, 'archetypes' => [ @@ -35,4 +35,20 @@ 'manager' => CAP_ALLOW, ], ], + 'tiny/elements:manage' => [ + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], + 'tiny/elements:showteachercomponents' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'teacher' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ], + ], ]; diff --git a/db/base.zip b/db/base.zip new file mode 100644 index 0000000..3cf13e0 Binary files /dev/null and b/db/base.zip differ diff --git a/db/caches.php b/db/caches.php new file mode 100644 index 0000000..57e7501 --- /dev/null +++ b/db/caches.php @@ -0,0 +1,32 @@ +. + +/** + * Cache definitions for tiny_elements. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$definitions = [ + \tiny_elements\local\constants::CACHE_AREA => [ + 'mode' => cache_store::MODE_APPLICATION, + ], +]; diff --git a/db/hooks.php b/db/hooks.php new file mode 100644 index 0000000..5077ac7 --- /dev/null +++ b/db/hooks.php @@ -0,0 +1,32 @@ +. + +/** + * Hook callbacks for tiny_elements. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); + +$callbacks = [ + [ + 'hook' => \core\hook\output\before_http_headers::class, + 'callback' => \tiny_elements\local\hook_callbacks::class . '::add_elements_data_to_dom', + ], +]; diff --git a/db/install.php b/db/install.php new file mode 100644 index 0000000..b3a48ea --- /dev/null +++ b/db/install.php @@ -0,0 +1,45 @@ +. + +/** + * Install script for Components for Learning (Elements) + * + * Documentation: {@link https://moodledev.io/docs/guides/upgrade} + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use tiny_elements\importer; + +/** + * Executed on installation of Components for Learning (Elements) + * + * @return bool + */ +function xmldb_tiny_elements_install() { + try { + $basezip = __DIR__ . '/base.zip'; + $importer = new importer(); + $importer->import($basezip); + } catch (Exception $e) { + debugging($e->getMessage(), DEBUG_NORMAL); + return false; + } + return true; +} diff --git a/db/install.xml b/db/install.xml new file mode 100644 index 0000000..9bff481 --- /dev/null +++ b/db/install.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+
diff --git a/db/services.php b/db/services.php new file mode 100644 index 0000000..72b14d6 --- /dev/null +++ b/db/services.php @@ -0,0 +1,80 @@ +. + +/** + * External service definitions for tiny_elements + * + * @package tiny_elements + * @copyright ISB Bayern, 2024 + * @author Dr. Peter Mayer + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$functions = [ + 'tiny_elements_get_elements_data' => [ + 'classname' => 'tiny_elements\external\get_elements_data', + 'description' => 'Retrieve all elements data', + 'type' => 'read', + 'ajax' => true, + 'capabilities' => 'tiny/elements:viewplugin', + ], + 'tiny_elements_delete_item' => [ + 'classname' => 'tiny_elements\external\delete_item', + 'methodname' => 'execute', + 'description' => 'Delete item.', + 'type' => 'write', + 'ajax' => true, + 'capabilities' => 'tiny/elements:manage', + ], + 'tiny_elements_duplicate_item' => [ + 'classname' => 'tiny_elements\external\duplicate_item', + 'methodname' => 'execute', + 'description' => 'Duplicate item.', + 'type' => 'write', + 'ajax' => true, + 'capabilities' => 'tiny/elements:manage', + ], + 'tiny_elements_get_variants' => [ + 'classname' => 'tiny_elements\external\get_variants', + 'description' => 'Retrieve variants data', + 'type' => 'read', + 'ajax' => true, + 'capabilities' => 'tiny/elements:manage', + ], + 'tiny_elements_get_flavors' => [ + 'classname' => 'tiny_elements\external\get_flavors', + 'description' => 'Retrieve flavors data', + 'type' => 'read', + 'ajax' => true, + 'capabilities' => 'tiny/elements:manage', + ], + 'tiny_elements_get_images' => [ + 'classname' => 'tiny_elements\external\get_images', + 'description' => 'Retrieve images data', + 'type' => 'read', + 'ajax' => true, + 'capabilities' => 'tiny/elements:manage', + ], + 'tiny_elements_wipe' => [ + 'classname' => 'tiny_elements\external\wipe', + 'description' => 'Wipe all elements data', + 'type' => 'write', + 'ajax' => true, + 'capabilities' => 'tiny/elements:manage', + ], +]; diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 0000000..13021a0 --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,190 @@ +. + +/** + * Upgrade steps for Components for Learning (Elements) + * + * Documentation: {@link https://moodledev.io/docs/guides/upgrade} + * + * @package tiny_elements + * @category upgrade + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Execute the plugin upgrade steps from the given old version. + * + * @param int $oldversion + * @return bool + */ +function xmldb_tiny_elements_upgrade($oldversion): bool { + global $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2025013100) { + // Define field displayorder to be added to tiny_elements_flavor. + $table = new xmldb_table('tiny_elements_flavor'); + $field = new xmldb_field('displayorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'displayname'); + + // Conditionally launch add field displayorder. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Elements savepoint reached. + upgrade_plugin_savepoint(true, 2025013100, 'tiny', 'elements'); + } + + if ($oldversion < 2025022402) { + // Define field componentname to be added to tiny_elements_comp_variant. + $table = new xmldb_table('tiny_elements_comp_variant'); + $field = new xmldb_field('componentname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'variant'); + + // Conditionally launch add field componentname. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Move componentid to componentname. + $DB->execute( + "UPDATE {tiny_elements_comp_variant} + SET componentname = (SELECT name FROM {tiny_elements_component} WHERE id = component)" + ); + + // Delete all rows without componentname. + $DB->execute("DELETE FROM {tiny_elements_comp_variant} WHERE componentname IS NULL"); + + // Remove old foreign key. + $key = new xmldb_key( + 'tinyelementscompvariant_comp_fk', + XMLDB_KEY_FOREIGN, + ['component'], + 'tiny_elements_component', + ['id'] + ); + + // Launch drop key tinyelementscompvariant_comp_fk. + $dbman->drop_key($table, $key); + + // Add new foreign key. + $key = new xmldb_key( + 'tinyelementscompvariant_comp_fk', + XMLDB_KEY_FOREIGN, + ['componentname'], + 'tiny_elements_component', + ['name'] + ); + + // Launch add key tinyelementscompvariant_comp_fk. + $dbman->add_key($table, $key); + + $field = new xmldb_field('component'); + + // Conditionally launch drop field component. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Elements savepoint reached. + upgrade_plugin_savepoint(true, 2025022402, 'tiny', 'elements'); + } + + if ($oldversion < 2025022409) { + $manager = new tiny_elements\manager(); + + // Define field categoryname to be added to tiny_elements_component. + $table = new xmldb_table('tiny_elements_component'); + $field = new xmldb_field('categoryname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'compcat'); + + // Conditionally launch add field categoryname. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $DB->execute( + "UPDATE {tiny_elements_component} + SET categoryname = (SELECT name FROM {tiny_elements_compcat} WHERE id = compcat)" + ); + + $key = new xmldb_key('compcat', XMLDB_KEY_FOREIGN, ['compcat'], 'tiny_elements_compcat', ['id']); + + // Launch drop key compcat. + $dbman->drop_key($table, $key); + + // Define key tinyelementscomp_comp_fk (foreign) to be added to tiny_elements_component. + $key = new xmldb_key('tinyelementscomp_comp_fk', XMLDB_KEY_FOREIGN, ['categoryname'], 'tiny_elements_compcat', ['name']); + + // Launch add key tinyelementscomp_comp_fk. + $dbman->add_key($table, $key); + + // Define field categoryname to be added to tiny_elements_variant. + $table = new xmldb_table('tiny_elements_variant'); + $field = new xmldb_field('categoryname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'displayname'); + + // Conditionally launch add field categoryname. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $variants = $DB->get_records('tiny_elements_variant'); + foreach ($variants as $variant) { + $compcatname = $manager->get_compcatname_for_variant($variant->name); + $DB->set_field('tiny_elements_variant', 'categoryname', $compcatname, ['id' => $variant->id]); + } + + $key = new xmldb_key('tinyelementsvari_comp_fk', XMLDB_KEY_FOREIGN, ['categoryname'], 'tiny_elements_compcat', ['name']); + + // Launch add key tinyelementsvari_comp_fk. + $dbman->add_key($table, $key); + + // Define field categoryname to be added to tiny_elements_glavor. + $table = new xmldb_table('tiny_elements_flavor'); + $field = new xmldb_field('categoryname', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'displayname'); + + // Conditionally launch add field categoryname. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $flavors = $DB->get_records('tiny_elements_flavor'); + foreach ($flavors as $flavor) { + $compcatname = $manager->get_compcatname_for_flavor($flavor->name); + $DB->set_field('tiny_elements_flavor', 'categoryname', $compcatname, ['id' => $flavor->id]); + } + + $key = new xmldb_key('tinyelementsflav_comp_fk', XMLDB_KEY_FOREIGN, ['categoryname'], 'tiny_elements_compcat', ['name']); + + // Launch add key tinyelementsflav_comp_fk. + $dbman->add_key($table, $key); + + // Drop old compcat field. + $table = new xmldb_table('tiny_elements_component'); + $field = new xmldb_field('compcat'); + + // Conditionally launch drop field compcat. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Elements savepoint reached. + upgrade_plugin_savepoint(true, 2025022409, 'tiny', 'elements'); + } + + return true; +} diff --git a/editor_styles.css b/editor_styles.css index df90478..b587c56 100644 --- a/editor_styles.css +++ b/editor_styles.css @@ -1 +1 @@ -.c4l-spacer{display:block;height:12px}.c4l-spacer+.c4l-keyconcept,.c4l-spacer+.c4lv-keyconcept,.c4l-spacer+.c4l-tip,.c4l-spacer+.c4lv-tip,.c4l-spacer+.c4l-reminder,.c4l-spacer+.c4lv-reminder,.c4l-spacer+.c4l-attention,.c4l-spacer+.c4lv-attention,.c4l-spacer+.c4l-quote,.c4l-spacer+.c4lv-quote,.c4l-spacer+.c4l-example,.c4l-spacer+.c4lv-example,.c4l-spacer+.c4l-expectedfeedback,.c4l-spacer+.c4lv-expectedfeedback,.c4l-spacer+.c4l-learningoutcomes,.c4l-spacer+.c4lv-learningoutcomes{margin:0 auto}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-do,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-do{margin-top:0}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-dont,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-dont{margin-bottom:0}.c4l-spacer+.c4l-readingcontext,.c4l-spacer+.c4lv-readingcontext{margin-top:16px;margin-bottom:4px}.c4l-spacer+.c4l-figure,.c4l-spacer+.c4lv-figure{margin:24px auto 14px auto}.c4l-spacer+.c4l-proceduralcontext,.c4l-spacer+.c4lv-proceduralcontext{margin-bottom:0;padding-top:0;padding-bottom:0}.c4l-inline-group{display:flex;flex-direction:row;align-content:flex-end;justify-content:flex-end}.c4l-display-left{display:flex;flex-direction:row;align-content:flex-start;justify-content:flex-start}.c4l-spacer+.c4l-display-left{margin-top:0}.c4l-embedded-caption{font-size:13px;margin-top:12px;text-align:right}.c4l-embedded-caption span{text-transform:uppercase;font-size:14px}.c4l-embedded-caption span::after{content:", "}.c4l-keyconcept,.c4lv-keyconcept{min-width:200px;max-width:99%;background-color:#f1f5fe;padding:24px 36px 30px 36px;border:none;border-left:6px solid #387af1;margin:36px auto;position:relative;border-radius:0}.c4l-keyconcept p:last-of-type,.c4lv-keyconcept p:last-of-type{margin-bottom:0}.c4l-tip,.c4lv-tip{min-width:200px;max-width:99%;background-color:#fbeffa;padding:24px 48px 30px 36px;border:none;border-left:6px solid #b00ca9;margin:36px auto;position:relative;border-radius:0}.c4l-tip p:last-of-type,.c4lv-tip p:last-of-type{margin-bottom:0}.c4l-tip::after,.c4lv-tip::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_tip]]);position:absolute;top:6px;right:-3px}.c4l-reminder,.c4lv-reminder{min-width:200px;max-width:99%;background-color:#eff8fd;padding:24px 48px 30px 36px;border:none;border-left:6px solid #16b9ff;margin:36px auto;position:relative;border-radius:0}.c4l-reminder p:last-of-type,.c4lv-reminder p:last-of-type{margin-bottom:0}.c4l-reminder::after,.c4lv-reminder::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_reminder]]);position:absolute;top:6px;right:-3px}.c4l-attention,.c4lv-attention{min-width:200px;max-width:99%;background-color:#fef6ed;padding:24px 48px 30px 36px;border:none;border-left:6px solid #f88923;margin:36px auto;position:relative;border-radius:0}.c4l-attention p:last-of-type,.c4lv-attention p:last-of-type{margin-bottom:0}.c4l-attention::after,.c4lv-attention::after{content:url([[pix:tiny_c4l|c4l_attention]]);position:absolute;top:6px;right:-3px}.c4l-quote,.c4lv-quote{font-family:Iowan Old Style,Apple Garamond,Baskerville,Times New Roman,Droid Serif,Times,Source Serif Pro,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:16px;line-height:24px;margin:24px auto;min-width:200px;max-width:100%}.c4l-quote .c4l-quote-body,.c4lv-quote .c4l-quote-body{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:flex-start;align-items:stretch;align-content:space-between;font-style:italic}.c4l-quote .c4l-quote-line,.c4lv-quote .c4l-quote-line{border-left:4px solid #387af1;margin-right:16px;margin-top:4px;margin-bottom:2px}.c4l-quote .c4l-quote-text::before,.c4lv-quote .c4l-quote-text::before{content:"";position:static;top:0;margin-right:0}.c4l-quote .c4l-quote-text::after,.c4lv-quote .c4l-quote-text::after{content:"";position:static;top:0;margin-left:0}.c4l-quote .c4l-quote-text p:first-of-type::before,.c4lv-quote .c4l-quote-text p:first-of-type::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_quote_open]]);position:relative;top:-4px;margin-right:2px}.c4l-quote .c4l-quote-text p:last-of-type::after,.c4lv-quote .c4l-quote-text p:last-of-type::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_quote_close]]);position:relative;top:-2px;margin-left:2px}.c4l-quote .c4l-quote-text p:last-of-type,.c4lv-quote .c4l-quote-text p:last-of-type{margin-bottom:0}.c4l-quote .c4l-quote-caption{font-size:13px;margin-top:12px;text-align:right}.c4l-quote .c4l-quote-caption span{text-transform:uppercase;font-size:14px}.c4l-quote .c4l-quote-caption span::after{content:", "}.c4l-dodontcards .c4l-dodontcards-do,.c4l-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-do{margin-top:36px}.c4l-dodontcards .c4l-dodontcards-dont,.c4l-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-dont{margin-bottom:36px}.c4l-dodontcards>.c4l-dodontcards-dont,.c4lv-dodontcards>.c4l-dodontcards-dont{margin-top:12px}.c4l-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-do{min-width:200px;max-width:100%;background:#f1fbf5;border-radius:10px;padding:24px 48px 30px 36px;margin:12px auto;position:relative}.c4l-dodontcards .c4l-dodontcards-do::before,.c4lv-dodontcards .c4l-dodontcards-do::before{content:url([[pix:tiny_c4l|c4l_docard]]);position:absolute;top:12px;right:12px}.c4l-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-dont{min-width:200px;max-width:100%;background:#ffefef;border-radius:10px;padding:24px 48px 30px 36px;margin:12px auto;position:relative}.c4l-dodontcards .c4l-dodontcards-dont::before,.c4lv-dodontcards .c4l-dodontcards-dont::before{content:url([[pix:tiny_c4l|c4l_dontcard]]);position:absolute;top:12px;right:12px}.c4l-dodontcards .c4l-dodontcards-do p,.c4l-dodontcards .c4l-dodontcards-dont p,.c4lv-dodontcards .c4l-dodontcards-do p,.c4lv-dodontcards .c4l-dodontcards-dont p{margin-bottom:6px}.c4l-dodontcards .c4l-dodontcards-do p:last-of-type,.c4l-dodontcards .c4l-dodontcards-dont p:last-of-type,.c4lv-dodontcards .c4l-dodontcards-do p:last-of-type,.c4lv-dodontcards .c4l-dodontcards-dont p:last-of-type{margin-bottom:0}.c4l-readingcontext,.c4lv-readingcontext{min-width:200px;max-width:75%;background-color:#fff;box-shadow:0 4px 24px rgba(0,0,0,.08);box-sizing:border-box;margin:36px auto}.c4l-readingcontext p,.c4lv-readingcontext p{font-size:16px;line-height:23px}.c4l-readingcontext{padding:30px 40px 19px 40px;font-family:Iowan Old Style,Apple Garamond,Baskerville,Times New Roman,Droid Serif,Times,Source Serif Pro,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.c4l-readingcontext .c4l-readingcontext-caption{font-size:16px;margin-top:24px;text-align:right;padding-bottom:14px;font-style:italic}.c4l-readingcontext .c4l-readingcontext-caption span{text-transform:uppercase;font-size:16px;font-style:normal}.c4l-readingcontext .c4l-readingcontext-caption span::after{content:", "}.c4lv-readingcontext{padding:30px 40px 32px 40px;font-family:sans-serif}.c4lv-readingcontext p:last-of-type{margin-bottom:0}.c4lv-readingcontext .c4l-embedded-caption{margin-top:1rem;font-size:16px;font-style:italic}.c4lv-readingcontext.c4l-comfort-reading-variant{font-family:Iowan Old Style,Apple Garamond,Baskerville,Times New Roman,Droid Serif,Times,Source Serif Pro,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.c4l-example,.c4lv-example{font-size:15px;line-height:22px;color:inherit;background:#fff;border-radius:0;margin:36px auto;min-width:75%;max-width:100%;padding:18px 24px;position:relative;box-shadow:0 4px 24px rgba(0,0,0,.13)}.c4l-example h1,.c4lv-example h1{font-weight:700;font-size:11px;line-height:21px;letter-spacing:.5px;color:#3171e3;margin:0 0 24px 0;text-transform:uppercase;font-family:inherit;display:inline-block;border-bottom:2px solid #3171e3}.c4l-figure,.c4lv-figure{min-width:200px;margin:48px auto}.c4l-figure img,.c4lv-figure img{width:100%}.c4l-figure img:not([src]),.c4l-figure img[src=""],.c4lv-figure img:not([src]),.c4lv-figure img[src=""]{content:url([[pix:tiny_c4l|c4l_figure_placeholder]])}.c4l-figure figcaption,.c4lv-figure figcaption{font-size:13px;line-height:16px;color:#686d79;margin-top:7px}.c4l-figure .c4l-figure-footer::after,.c4lv-figure .c4l-figure-footer::after{content:" | ";font-weight:normal;font-style:normal}.c4l-figure .c4l-figure-caption,.c4lv-figure .c4l-figure-caption{font-style:normal;font-size:12px}.c4l-figure figcaption span strong,.c4lv-figure figcaption span strong{font-weight:700}.c4l-figure{max-width:100%}.c4lv-figure{max-width:75%}.c4l-tag,.c4lv-tag{display:inline-block;font-size:10px;font-weight:700;color:#2167cf;background-color:#f1f5fe;border-radius:30px;padding:4px 17px;line-height:20px;margin-bottom:24px;text-transform:uppercase;letter-spacing:.2px}.c4lv-tag.c4l-align-right-variant{margin-left:auto}.c4l-estimatedtime,.c4lv-estimatedtime{font-size:12px;color:#2167cf;background-color:#f1f5fe;padding:6px 14px;font-weight:700;margin-left:6px;margin-bottom:24px;padding-top:5px;padding-bottom:5px;padding-left:36px;position:relative;border-radius:5px}.c4l-estimatedtime span,.c4lv-estimatedtime span{font-weight:600;font-size:10px}.c4l-estimatedtime::before,.c4lv-estimatedtime::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_estimatedtime]]);position:absolute;left:12px}.c4lv-estimatedtime.c4l-align-left-variant{margin-right:auto;margin-left:0}.c4l-duedate,.c4lv-duedate{font-size:12px;color:#2167cf;background-color:#f1f5fe;padding:6px 14px;font-weight:600;margin-left:6px;margin-bottom:24px;padding-top:5px;padding-bottom:5px;padding-left:36px;position:relative;border-radius:5px}.c4l-duedate::after,.c4lv-duedate::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_duedate]]);position:absolute;left:12px}.c4lv-duedate.c4l-align-left-variant{margin-right:auto;margin-left:0}.c4l-proceduralcontext,.c4lv-proceduralcontext{font-style:italic;color:#3a56af;margin-bottom:12px;padding-top:24px;padding-bottom:24px;font-weight:400}.c4l-gradingvalue,.c4lv-gradingvalue{font-size:12px;color:#2167cf;background-color:#f1f5fe;padding:6px 14px;font-weight:700;margin-left:6px;margin-bottom:24px;padding-top:5px;padding-bottom:5px;padding-left:36px;position:relative;border-radius:5px}.c4l-gradingvalue span,.c4lv-gradingvalue span{font-weight:600;font-size:10px}.c4l-gradingvalue::after,.c4lv-gradingvalue::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_gradingvalue]]);position:absolute;left:12px;top:6px}.c4lv-gradingvalue.c4l-align-left-variant{margin-right:auto;margin-left:0}.c4l-expectedfeedback,.c4lv-expectedfeedback{min-width:200px;max-width:90%;background-color:#fff;padding:24px 36px 30px 36px;margin:36px auto;font-style:italic;position:relative;border-radius:8px;border:none;box-shadow:0 4px 24px rgba(0,0,0,.13)}.c4l-expectedfeedback::before,.c4lv-expectedfeedback::before{content:""}.c4l-expectedfeedback::after,.c4lv-expectedfeedback::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_expectedfeedback]]);position:absolute;bottom:6px;right:-3px}.c4l-expectedfeedback p:last-of-type,.c4lv-expectedfeedback p:last-of-type{margin-bottom:0}.c4l-allpurposecard,.c4lv-allpurposecard{min-width:200px;max-width:100%;background:#f1f5fe;border-radius:10px;padding:24px 48px 30px 36px;margin:24px auto;position:relative}.c4l-allpurposecard p,.c4lv-allpurposecard p{margin-bottom:6px}.c4l-allpurposecard p:last-of-type,.c4lv-allpurposecard p:last-of-type{margin-bottom:0}.c4l-spacer+.c4l-allpurposecard{margin:0 auto}.c4l-inlinetag,.c4lv-inlinetag{font-weight:900;font-size:10px;text-transform:uppercase;letter-spacing:.2px;display:inline-block;color:#fff;background:#3171e3;border-radius:7px;padding:2px 7px 1px 7px;position:relative;top:-2px;margin-left:4px;margin-right:4px}.c4l-learningoutcomes,.c4lv-learningoutcomes{min-width:200px;max-width:99%;background-color:#f2f5fd;padding:24px 48px 30px 36px;border:none;margin:36px auto;position:relative;border-radius:0}.c4l-learningoutcomes p:last-of-type,.c4lv-learningoutcomes p:last-of-type{margin-bottom:0}.c4l-learningoutcomes .c4l-learningoutcomes-title,.c4lv-learningoutcomes .c4l-learningoutcomes-title{position:relative;top:-11px;left:-39px;padding:6px 14px 5px;border-top-right-radius:3px;border-bottom-right-radius:3px;font-weight:600;font-size:11px;letter-spacing:.7px;color:#fff;background-color:#497ae9;text-transform:uppercase;font-family:inherit;display:inline-block;margin-top:0;filter:drop-shadow(0 1.55601px 3.11202px rgba(0, 0, 0, 0.07))}.c4l-learningoutcomes .c4l-learningoutcomes-title::before,.c4lv-learningoutcomes .c4l-learningoutcomes-title::before{content:url([[pix:tiny_c4l|c4l_learningoutcomes_shadow]]);position:absolute;width:3px;height:2px;top:15.5px;left:.5px}.c4l-learningoutcomes .c4l-learningoutcomes-list,.c4lv-learningoutcomes .c4l-learningoutcomes-list{margin-top:18px;margin-bottom:18px;padding-left:32px}.c4l-learningoutcomes .c4l-learningoutcomes-list>li,.c4lv-learningoutcomes .c4l-learningoutcomes-list>li{position:relative}.c4l-learningoutcomes .c4l-learningoutcomes-list>li:not(:last-child),.c4lv-learningoutcomes .c4l-learningoutcomes-list>li:not(:last-child){margin-bottom:21px}.c4l-learningoutcomes .c4l-learningoutcomes-list li::before,.c4lv-learningoutcomes .c4l-learningoutcomes-list li::before{background-image:url([[pix:tiny_c4l|c4l_learningoutcomes_list_item]]);background-size:9px 11px;display:inline-block;width:9px;height:11px;content:"";justify-self:center;position:absolute;left:-32px;top:7px}.c4l-learningoutcomes .c4l-learningoutcomes-list li::marker,.c4lv-learningoutcomes .c4l-learningoutcomes-list li::marker{color:rgba(0,0,0,0)}.c4lv-learningoutcomes.c4l-ordered-list-variant .c4l-learningoutcomes-list{counter-reset:section}.c4lv-learningoutcomes.c4l-ordered-list-variant .c4l-learningoutcomes-list li::before{background-image:none;position:absolute;left:-32px;top:0;counter-increment:section;content:counter(section) ". ";font-weight:700;color:#497ae9}body.mce-content-body .collapse:not(.show),.c4l-code-preview .collapse:not(.show){display:block !important}@media only screen and (min-width: 576px){.c4l-modal.modal-open .modal-dialog,.c4l-modal-no-preview.modal-open .modal-dialog{min-width:550px}.c4l-select-filters{display:none}.c4l-buttons-filters{display:flex;justify-content:center;align-items:center;margin:16px 0 37px}.c4l-spacer+.c4l-keyconcept,.c4l-spacer+.c4lv-keyconcept,.c4l-spacer+.c4l-tip,.c4l-spacer+.c4lv-tip,.c4l-spacer+.c4l-reminder,.c4l-spacer+.c4lv-reminder,.c4l-spacer+.c4l-attention,.c4l-spacer+.c4lv-attention,.c4l-spacer+.c4l-expectedfeedback,.c4l-spacer+.c4lv-expectedfeedback,.c4l-spacer+.c4l-learningoutcomes,.c4l-spacer+.c4lv-learningoutcomes{margin:24px auto 8px auto}.c4l-spacer+.c4l-quote,.c4l-spacer+.c4lv-quote{margin:12px auto 0 auto}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-do,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-do{margin-top:0}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-dont,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-dont{margin-bottom:0}.c4l-spacer+.c4l-example,.c4l-spacer+.c4lv-example{margin:12px auto 6px auto;padding:36px 48px}.c4l-embedded-caption{margin-top:24px}.c4l-quote,.c4lv-quote{margin:36px auto;max-width:90%}.c4l-quote .c4l-quote-caption{margin-top:24px}.c4l-dodontcards .c4l-dodontcards-do,.c4l-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-dont{max-width:90%;margin:24px auto}.c4l-readingcontext,.c4lv-readingcontext{max-width:88%}.c4l-example,.c4lv-example{max-width:88%;padding:36px 48px}.c4l-expectedfeedback,.c4lv-expectedfeedback{max-width:88%;margin:48px auto}.c4l-allpurposecard,.c4lv-allpurposecard{margin:36px auto;max-width:90%}}@media only screen and (min-width: 768px){.c4l-keyconcept,.c4lv-keyconcept,.c4l-tip,.c4lv-tip,.c4l-reminder,.c4lv-reminder,.c4l-attention,.c4lv-attention,.c4l-dodontcards-do,.c4lv-dodontcards-do,.c4l-dodontcards-dont,.c4lv-dodontcards-dont,.c4l-readingcontext,.c4lv-readingcontext,.c4l-expectedfeedback,.c4lv-expectedfeedback,.c4l-allpurposecard,.c4lv-allpurposecard,.c4l-learningoutcomes,.c4lv-learningoutcomes{max-width:75%}.c4l-keyconcept.c4l-full-width-variant,.c4lv-keyconcept.c4l-full-width-variant,.c4l-tip.c4l-full-width-variant,.c4lv-tip.c4l-full-width-variant,.c4l-reminder.c4l-full-width-variant,.c4lv-reminder.c4l-full-width-variant,.c4l-attention.c4l-full-width-variant,.c4lv-attention.c4l-full-width-variant,.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-do,.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-do,.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4l-readingcontext.c4l-full-width-variant,.c4lv-readingcontext.c4l-full-width-variant,.c4l-expectedfeedback.c4l-full-width-variant,.c4lv-expectedfeedback.c4l-full-width-variant,.c4l-allpurposecard.c4l-full-width-variant,.c4lv-allpurposecard.c4l-full-width-variant,.c4l-learningoutcomes.c4l-full-width-variant,.c4lv-learningoutcomes.c4l-full-width-variant,.c4l-quote.c4l-full-width-variant,.c4lv-quote.c4l-full-width-variant,.c4l-example.c4l-full-width-variant,.c4lv-example.c4l-full-width-variant{max-width:100%}.c4l-quote,.c4lv-quote{margin:48px auto;max-width:75%}.c4l-spacer+.c4l-quote,.c4l-spacer+.c4lv-quote{margin:24px auto 14px auto}.c4l-example,.c4lv-example{margin:48px auto;max-width:75%}.c4l-spacer+.c4l-example,.c4l-spacer+.c4lv-example{margin:24px auto 12px auto}}@media only screen and (min-width: 992px){.c4l-modal-no-preview.modal-open .modal-dialog{max-width:550px}.c4l-buttons-preview{max-height:314px}.c4l-buttons-preview.c4l-no-preview{justify-content:center;max-height:324px}.c4l-buttons-preview.c4l-no-preview .c4l-buttons-grid{grid-gap:9px;justify-content:center;width:405px}.c4l-buttons-preview.c4l-no-preview .c4l-code-preview{display:none}.c4l-buttons-grid{display:grid;grid-template-columns:repeat(auto-fill, 116px);grid-template-rows:repeat(auto-fill, 100px);grid-gap:4px;justify-content:flex-start;width:377px;padding:3px 0 3px 3px}.c4lt-dialog-button.c4l-custom-icon .c4l-button-text i{top:20px;left:0;right:0;margin:0 auto;max-width:25px;min-width:25px;width:100%;border-right:none}.c4lt-dialog-button{height:100px;width:116px;max-width:116px;box-shadow:none;border-radius:4px;margin-bottom:0}.c4lt-dialog-button .c4l-button-text::before{top:20px;left:0;right:0;height:20px;width:26px;margin:0 auto;border-right:none}.c4lt-dialog-button .c4l-button-text{padding:55px 8px 15px 8px;font-size:12px;font-weight:500;line-height:13px;width:80px;text-align:center;justify-content:center}.c4lt-dialog-button .c4l-button-variants{grid-auto-flow:row;grid-gap:0;align-content:flex-start;justify-content:start;width:36px;height:99px;background-color:#ecf3ff;margin:0}.c4lt-dialog-button .c4l-button-variants .c4l-button-variant{display:flex;justify-content:center;align-items:center;color:#1679f9;height:33px;width:36px}.c4lt-dialog-button:hover,.c4lt-dialog-button:active{box-shadow:0 0 0 3px rgba(22,121,249,.3)}.c4lt-dialog-button .c4l-button-variants .c4l-button-variant:first-child,.c4lt-dialog-button .c4l-button-variants .c4l-button-variant:nth-child(2){border-bottom:1px solid #fff}.c4lt-dialog-button:hover .c4l-button-variants .c4l-button-variant{box-shadow:none}.c4l-code-preview{position:relative;display:flex;flex-direction:column;height:300px;width:343px;align-items:center;justify-content:center}.c4l-preview-default{border:1px solid #e1e5ee;border-radius:8px;color:#9297a1;padding:23px 10px;font-weight:400;font-size:12px;line-height:16px;text-align:center;width:60%;margin:0 auto;-webkit-user-select:none;-ms-user-select:none;user-select:none}.c4l-text-preview{position:absolute;top:10px;right:10px;font-weight:600;font-size:9.5px;line-height:11px;letter-spacing:.06em;color:#fff;background-color:#535d76;border-radius:6px;padding:5px;text-transform:uppercase}}.c4l-keyconcept.c4l-full-width-variant,.c4l-quote.c4l-full-width-variant,.c4l-dodontcards.c4l-full-width-variant,.c4l-dodontcards-do,.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4l-figure.c4l-full-width-variant,.c4l-proceduralcontext.c4l-full-width-variant,.c4l-learningoutcomes.c4l-full-width-variant,.c4l-allpurposecard.c4l-full-width-variant{max-width:100%}.c4l-tip.c4l-full-width-variant,.c4l-reminder.c4l-full-width-variant,.c4l-attention.c4l-full-width-variant{max-width:99%}.c4l-readingcontext.c4l-full-width-variant,.c4l-example.c4l-full-width-variant,.c4l-expectedfeedback.c4l-full-width-variant{max-width:94%}.c4lv-keyconcept.c4l-full-width-variant,.c4lv-quote.c4l-full-width-variant,.c4lv-dodontcards.c4l-full-width-variant,.c4lv-dodontcards-do,.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4lv-figure.c4l-full-width-variant,.c4lv-proceduralcontext.c4l-full-width-variant,.c4lv-learningoutcomes.c4l-full-width-variant,.c4lv-expectedfeedback.c4l-full-width-variant,.c4lv-allpurposecard.c4l-full-width-variant,.c4lv-custom-component.c4l-full-width-variant{max-width:100%}.c4lv-tip.c4l-full-width-variant,.c4lv-reminder.c4l-full-width-variant,.c4lv-attention.c4l-full-width-variant{max-width:99%}.c4lv-readingcontext.c4l-full-width-variant,.c4lv-example.c4l-full-width-variant,.c4lv-expectedfeedback.c4l-full-width-variant{max-width:94%} +body.mce-content-body .collapse:not(.show),.elements-code-preview .collapse:not(.show){display:block !important}@media only screen and (min-width: 576px){.elements-modal.modal-open .modal-dialog,.elements-modal-no-preview.modal-open .modal-dialog{min-width:550px}.elements-select-filters{display:none}.elements-buttons-filters,.elements-buttons-flavors{display:flex;justify-content:center;align-items:center;margin:10px 0 10px}}@media only screen and (min-width: 992px){.elements-modal-no-preview.modal-open .modal-dialog{max-width:550px}.elements-buttons-preview.elements-no-preview{justify-content:center}.elements-buttons-preview.elements-no-preview .elements-buttons-grid{grid-gap:9px;justify-content:center;width:405px}.elements-buttons-preview.elements-no-preview .elements-code-preview{display:none}.elements-buttons-grid{width:400px;padding:10px}.elementst-dialog-button.elements-custom-icon .elements-button-text i{top:20px;left:0;right:0;margin:0 auto;max-width:25px;min-width:25px;width:100%;border-right:none}.elementst-dialog-button{height:100px;width:116px;max-width:116px;box-shadow:none;border-radius:4px;margin-bottom:0}.elementst-dialog-button::before{content:""}.elementst-dialog-button .elements-button-text::before{left:0;right:0;margin:0 auto;border-right:none;display:inline-block;width:60px;height:60px;top:3px}.elementst-dialog-button .elements-button-text{padding:70px 8px 15px 8px;font-size:12px;font-weight:500;line-height:13px;width:80px;text-align:center;justify-content:center}.elementst-dialog-button .elements-button-variants{grid-auto-flow:row;grid-gap:0;align-content:flex-start;justify-content:start;width:36px;height:99px;background-color:#ecf3ff;margin:0}.elementst-dialog-button .elements-button-variants .elements-button-variant{display:flex;justify-content:center;align-items:center;color:#1679f9;height:33px;width:36px}.elementst-dialog-button:hover,.elementst-dialog-button:active{box-shadow:0 0 0 3px rgba(22,121,249,.3)}.elementst-dialog-button .elements-button-variants .elements-button-variant:first-child,.elementst-dialog-button .elements-button-variants .elements-button-variant:nth-child(2){border-bottom:1px solid #fff}.elementst-dialog-button:hover .elements-button-variants .elements-button-variant{box-shadow:none}.elements-code-preview{position:relative;display:flex;flex-direction:column;width:377px;align-items:center;justify-content:center}.elements-preview-default{border:1px solid #e1e5ee;border-radius:8px;color:#9297a1;padding:23px 10px;font-weight:400;font-size:12px;line-height:16px;text-align:center;width:60%;margin:0 auto;-webkit-user-select:none;-ms-user-select:none;user-select:none}.elements-text-preview{position:absolute;top:10px;right:10px;font-weight:600;font-size:9.5px;line-height:11px;letter-spacing:.06em;color:#fff;background-color:#535d76;border-radius:6px;padding:5px;text-transform:uppercase}} diff --git a/lang/de/tiny_elements.php b/lang/de/tiny_elements.php new file mode 100644 index 0000000..32f5f42 --- /dev/null +++ b/lang/de/tiny_elements.php @@ -0,0 +1,132 @@ +. + +/** + * Plugin Elements strings for language de. + * + * @package tiny_elements + * @category string + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['additem'] = 'Hinzufügen'; +$string['bulk_edit_displaynames'] = 'Editieren aller Anzeigenamen'; +$string['bulk_edit_flavor_displaynames'] = 'Editieren aller Flavor Anzeigenamen'; +$string['bulk_edit_variant_displaynames'] = 'Editieren aller Variant Anzeigenamen'; +$string['button_elements'] = 'Elemente'; +$string['c4lcompatibility'] = 'Wenn ausgewählt, lautet der Varianten-Klassenname c4l-...-variant anstelle von elements-...-variant, um mit den ursprünglichen c4l-Komponenten kompatibel zu sein.'; +$string['c4lcompatibility_help'] = 'C4L (components for learning) ist das Plugin auf dem Elements basiert.'; +$string['cachedef_tiny_elements_css'] = 'Cache für tiny_elements CSS'; +$string['category'] = 'Kategorie'; +$string['close'] = 'Schließen'; + +$string['code'] = 'HTML'; +$string['code_help'] = 'Einzufügender HTML-Code. Sie können {{VARIANTS}}, {{FLAVOR}} und {{PLACEHOLDER}} als Platzhalter für Varianten, Flavors und einzufügenden Text verwenden.'; +$string['compcat'] = 'Kategorien'; +$string['compflavor_icons'] = 'Ändern der Symbole für Komponenten je nach Flavor'; +$string['component'] = 'Komponente'; +$string['component_flavor'] = 'Komponente/Flavor'; +$string['componentname'] = 'Komponentenname'; +$string['componentname_help'] = 'Name der Komponente zur internen Verwendung (auch als Klassenname in CSS)'; +$string['components'] = 'Komponenten'; +$string['content'] = 'Inhalt'; +$string['copyof'] = 'Kopie von {$a}'; +$string['css'] = 'CSS'; +$string['delete'] = 'Element "{$a}" löschen'; +$string['deletewarning'] = 'Sind Sie sicher, dass Sie dieses Element löschen möchten?'; +$string['displayname'] = 'Anzeigename'; +$string['displayname_help'] = 'Name der Komponente, der den Benutzern angezeigt wird'; +$string['displayorder'] = 'Anzeigereihenfolge'; +$string['dryrun'] = 'Import simulieren'; +$string['dryrun_help'] = 'Wenn diese Option aktiviert ist, wird ein Import simuliert ohne Änderungen vorzunehmen. Damit kann festgestellt werden, ob durch den Import bestehende Objekte verändert werden.'; +$string['edititem'] = 'Element bearbeiten'; +$string['editlicenses'] = 'Quellenangaben der Symbole bearbeiten'; +$string['editlicensesformfileautor_help'] = 'Wer hat das Werk geschaffen?
Hier wird der Autor, Künstler + oder wenn keine Person genannt ist, die herausgebende Institution angegeben.

+ Üblich ist dabei folgendes Schema:
Nachname, Vorname. Herausgeber werden durch "Hrsg." am Schluss hervorgehoben. + Im Falle mehrerer Autoren werden diese durch Schrägstriche voneinander getrennt angegeben.
Sind auf einer Website + keine Autoren genannt, tritt der Name der Website bzw. der herausgebenden Institution an Autorenstelle.'; +$string['editlicensesformfilelicense_help'] = 'Geben Sie bitte an, unter welcher Lizenz das verwendet Material steht.'; +$string['editlicensesformfileurl'] = 'Quelle oder URL'; +$string['editlicensesformfileurl_help'] = 'Wo wurde das Werk veröffentlicht?

Bei Büchern ist der Verlag + und der Verlagsort, für Zeitschriftenartikel der Name der Zeitschrift plus Jahr-, Band-, Heft- und Seitenangabe, bei + Webseiten die vollständige URL anzugeben.'; +$string['elements:manage'] = 'Komponenten verwalten'; +$string['elements:showteachercomponents'] = 'Komponenten anzeigen, die nur für Lehrkräfte bestimmt sind'; +$string['elements:viewplugin'] = 'Kurselemente-Plugin anzeigen'; +$string['enablepreview'] = 'Vorschau aktivieren'; +$string['enablepreview_desc'] = 'Wenn aktiviert, wird eine Vorschau angezeigt, wenn Sie mit der Maus über jede Komponente fahren.'; +$string['error_export'] = 'Fehler beim Erstellen der Exportdatei'; +$string['error_fileimport'] = 'Fehler beim Importieren der Datei "{$a}"'; +$string['error_import_component'] = 'Fehler beim Importieren der Komponente "{$a}"'; +$string['error_import_missing_table'] = 'Fehler beim Importieren der XML-Datei: Tabelle "{$a}" fehlt'; +$string['errorbackupfile'] = 'Fehler in der Sicherungsdatei'; +$string['errorcompcat'] = '**compcat** darf nicht leer sein'; +$string['errordisplayname'] = 'Anzeigename darf nicht leer sein'; +$string['errorname'] = 'Name darf nicht leer sein'; +$string['export'] = 'Exportieren'; +$string['files'] = 'Dateien'; +$string['flavor'] = 'Flavor'; +$string['flavors'] = 'Flavors'; +$string['foundcompcat'] = 'Nicht zugewiesene Elemente'; +$string['generalsettings'] = 'Allgemeine Einstellungen'; +$string['hideforstudents'] = 'Verbergen für Schüler'; +$string['iconurl'] = 'Symbol-URL'; +$string['import'] = 'Importieren'; +$string['import_simulation'] = 'Import simulieren'; +$string['js'] = 'JS'; +$string['linktomanagerdesc'] = 'Gehen Sie zu Verwaltung, um Änderungen vorzunehmen.'; +$string['linktomanagername'] = 'Link zur Verwaltung'; +$string['manage'] = 'Verwalten'; +$string['management'] = 'Verwaltung'; +$string['menuitem_elements'] = 'Kurselemente'; +$string['name'] = 'Name'; +$string['newcategory'] = 'Neue Kategorie "{$a}"'; +$string['newcompflavor'] = 'Erzeuge Beziehung Komponente <-> Geschmacksrichtung "{$a}"'; +$string['newcomponent'] = 'Neue Komponente "{$a}"'; +$string['newcompvariant'] = 'Erzeuge Beziehung Komponente <-> Variante "{$a}"'; +$string['newfile'] = 'Neue Datei "{$a}"'; +$string['newflavor'] = 'Neue Geschmacksrichtung "{$a}"'; +$string['newmetadata'] = 'Neue Quellenangabe "{$a}"'; +$string['newmetadatafilemissing'] = 'Zugehörige Datei nicht gefunden: "{$a}"'; +$string['newvariant'] = 'Neue Variante "{$a}"'; +$string['pluginname'] = 'Kurselemente'; +$string['preview'] = 'Vorschau'; +$string['previewcss'] = 'Vorschau-CSS'; +$string['previewcsstext'] = 'Wenn alles korrekt ist, sollte die Komponente in allen Flavors angezeigt werden.'; +$string['previewdefault'] = 'Zeigen Sie mit dem Mauszeiger auf eine Komponente, um eine Vorschau anzuzeigen.'; +$string['privacy:preference:category'] = 'Bevorzugte Kategorie'; +$string['privacy:preference:components_flavors'] = 'Bevorzugte Flavors für jede Komponente'; +$string['privacy:preference:components_variants'] = 'Bevorzugte Varianten jeder Komponente'; +$string['replacecategory'] = 'Kategorie "{$a}" ersetzen'; +$string['replacecompflavor'] = 'Ersetze Beziehung Komponente <-> Geschmacksrichtung "{$a}"'; +$string['replacecomponent'] = 'Ersetze Komponente "{$a}"'; +$string['replacecompvariant'] = 'Ersetze Beziehung Komponente <-> Variante "{$a}"'; +$string['replacefile'] = 'Ersetze Datei "{$a}"'; +$string['replaceflavor'] = 'Ersetze Geschmacksrichtung "{$a}"'; +$string['replacevariant'] = 'Ersetze Variante "{$a}"'; +$string['showprinturls'] = 'Ein Symbol auswählen'; +$string['text'] = 'Text'; +$string['unchangedfile'] = 'Datei "{$a}" ist unverändert'; +$string['validclassname'] = 'Der Name muss ein gültiger CSS-Klassenname sein. Er darf nur Buchstaben, Zahlen und die Zeichen "-" und "_" enthalten. Er muss mit einem Buchstaben oder "_" beginnen. Es wird empfohlen, nur Kleinbuchstaben zu verwenden.'; +$string['variant'] = 'Variante'; +$string['variants'] = 'Varianten'; +$string['wipe'] = 'Alles löschen'; +$string['wipewarning'] = 'Diese Funktion entfernt alle Kategorien, Dateien, Komponenten, Flavors und Varianten. Es gibt keine Möglichkeit, diesen Schritt rückgängig zu machen. Stellen Sie sicher, dass Sie ein Backup haben!'; diff --git a/lang/en/tiny_c4l.php b/lang/en/tiny_c4l.php deleted file mode 100644 index 5076dd1..0000000 --- a/lang/en/tiny_c4l.php +++ /dev/null @@ -1,110 +0,0 @@ -. - -/** - * Plugin C4L strings for language en. - * - * @package tiny_c4l - * @category string - * @copyright 2022 Marc Català - * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['additionalhtml'] = 'Additional HTML admin page'; -$string['aimedatstudents'] = 'Aimed at Students'; -$string['aimedatstudents_desc'] = 'By default, only selected components will be available for users with student capabilities when using the editor. To change the default setting, just check or uncheck you own preferred selection.'; -$string['align-left'] = 'Align to left'; -$string['align-center'] = 'Align to center'; -$string['align-right'] = 'Align to right'; -$string['allpurposecard'] = 'All-purpose card'; -$string['attention'] = 'Attention'; -$string['button_c4l'] = 'C4L'; -$string['c4l:viewplugin'] = 'View C4L plugin'; -$string['caption'] = 'Caption'; -$string['comfort-reading'] = 'Comfort reading'; -$string['contextual'] = 'Contextual'; -$string['custom'] = 'Custom'; -$string['customcompcode'] = 'HTML code component {$a}'; -$string['customcompcodedesc'] = 'The word {{CUSTOMCLASS}} is a mandatory class to be beside your main component CSS classes.
-Code example: -
-<div class="{{CUSTOMCLASS}} <!-- Add your main CSS classes here -->">
-    <p>{{PLACEHOLDER}}</p>
-</div>
-
-Be aware that any Javascript code or inline CSS will be removed before rendering.'; -$string['customcompcount'] = 'Number of custom components'; -$string['customcompcountdesc'] = 'Number of custom components to be created'; -$string['customcompenable'] = 'Enable component {$a}'; -$string['customcompenabledesc'] = 'If enabled, this component will be available.'; -$string['customcompicon'] = 'Icon component {$a}'; -$string['customcompicondesc'] = 'Optional icon component. Recommended size: 18x18 pixels.'; -$string['customcompname'] = 'Button text component {$a}'; -$string['customcompnamedesc'] = 'Text showed inside the button.'; -$string['customcompsortorder'] = 'Sort order component {$a}'; -$string['customcompsortorderdesc'] = 'Sets the position of the component in the UI.'; -$string['customcomptext'] = 'Placeholder text component {$a}'; -$string['customcomptextdesc'] = 'Text to show as a placeholder in your component. Insert the word {{PLACEHOLDER}} in your code.'; -$string['customcomptitle'] = 'Custom component {$a}'; -$string['customcomponents'] = 'Custom components'; -$string['customcompvariant'] = 'Enable variants component {$a}'; -$string['customcompvariantdesc'] = 'If enabled, full width variant will be available for this component.'; -$string['customimagesbank'] = 'Bank of images'; -$string['customimagesbankdesc'] = 'To insert any of the uploaded images to your code, add this line:
-<img src="{{filename.extension}}" alt="Custom image">'; -$string['custompreviewcss'] = 'CSS code'; -$string['custompreviewcssdesc'] = 'CSS used to preview components inside the editor. -

Any CSS code added here must be also included either to your theme or inside the style tags <style>...<style> and saved into the additionalhtmlhead setting at {$a}; - otherwise your styles will not be applied to your components when rendered.

'; -$string['do-card'] = 'Do card'; -$string['dodontcards'] = 'Do/don\'t cards'; -$string['dont-card'] = 'Don\'t card'; -$string['dont-card-only'] = 'Don\'t card only'; -$string['duedate'] = 'Due date'; -$string['enablepreview'] = 'Enable preview'; -$string['enablepreview_desc'] = 'If enabled, a preview is showed when you hover the mouse cursor over each component.'; -$string['estimatedtime'] = 'Estimated time'; -$string['evaluative'] = 'Evaluative'; -$string['example'] = 'Example'; -$string['expectedfeedback'] = 'Expected feedback'; -$string['figure'] = 'Figure'; -$string['full-width'] = 'Full width'; -$string['generalsettings'] = 'General'; -$string['gradingvalue'] = 'Grading value'; -$string['helper'] = 'Helper'; -$string['helplinktext'] = 'C4L helper'; -$string['inlinetag'] = 'Inline tag'; -$string['keyconcept'] = 'Key concept'; -$string['learningoutcomes'] = 'Learning outcomes'; -$string['menuitem_c4l'] = 'Components for Learning (C4L)'; -$string['min'] = 'min'; -$string['notintendedforstudents'] = 'Not intended for Students '; -$string['notintendedforstudents_desc'] = 'By default, evaluative and procedural components are not intended for users with student capabilities to use in the editor. To change the default setting, check the components you would like to make available to the students.'; -$string['ordered-list'] = 'Ordered items'; -$string['pluginname'] = 'Components for Learning (C4L)'; -$string['preview'] = 'Preview'; -$string['previewdefault'] = 'Place the pointer on any component to see its preview.'; -$string['privacy:preference:components_variants'] = 'Preferred variants of each component'; -$string['procedural'] = 'Procedural'; -$string['proceduralcontext'] = 'Procedural context'; -$string['quote'] = 'Quote'; -$string['readingcontext'] = 'Reading context'; -$string['reminder'] = 'Reminder'; -$string['tag'] = 'Tag'; -$string['textplaceholder'] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; -$string['tip'] = 'Tip'; diff --git a/lang/en/tiny_elements.php b/lang/en/tiny_elements.php new file mode 100644 index 0000000..17790ea --- /dev/null +++ b/lang/en/tiny_elements.php @@ -0,0 +1,129 @@ +. + +/** + * Plugin Elements strings for language en. + * + * @package tiny_elements + * @category string + * @copyright 2022 Marc Català + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['additem'] = 'Add item'; +$string['bulk_edit_displaynames'] = 'Bulk edit displaynames'; +$string['bulk_edit_flavor_displaynames'] = 'Bulk edit flavor displaynames'; +$string['bulk_edit_variant_displaynames'] = 'Bulk edit variant displaynames'; +$string['button_elements'] = 'Elements'; +$string['c4lcompatibility'] = 'If checked, the variant class name will be c4l-...-variant instead of elements-...-variant for compatibility with the original c4l components.'; +$string['c4lcompatibility_help'] = 'c4l (components for learning) is the plugin elements is based on.'; +$string['cachedef_tiny_elements_css'] = 'Cache for tiny_elements CSS'; +$string['category'] = 'Category'; +$string['close'] = 'Close'; +$string['code'] = 'HTML'; +$string['code_help'] = 'HTML code to insert. You can use {{VARIANTS}}, {{FLAVOR}} and {{PLACEHOLDER}} as placeholders for variants, flavors and text to be inserted.'; +$string['compcat'] = 'Categories'; +$string['compflavor_icons'] = 'Change button icons for components depending on flavors'; +$string['component'] = 'Component'; +$string['component_flavor'] = 'Component/flavor'; +$string['componentname'] = 'Component name'; +$string['componentname_help'] = 'Name of the component use for internal use (also as a class name in CSS)'; +$string['components'] = 'Components'; +$string['content'] = 'Content'; +$string['copyof'] = 'Copy of {$a}'; +$string['css'] = 'CSS'; +$string['delete'] = 'Delete item "{$a}"'; +$string['deletewarning'] = 'Are you sure you want to delete this item.'; +$string['displayname'] = 'Display name'; +$string['displayname_help'] = 'Name of the component visible to the users'; +$string['displayorder'] = 'Display order'; +$string['dryrun'] = 'Simulate import'; +$string['dryrun_help'] = 'If checked, this will simulate an import run without changing anything. This can be used to see if there are any changes affecting existing items.'; +$string['edititem'] = 'Edit item'; +$string['editlicenses'] = 'Edit symbol licenses'; +$string['editlicensesformfileautor_help'] = 'Who created the work?
Here is the author, artist or if no person + is named, the issuing institution is stated.

The following scheme is usual:
Last name, first name. + Editors are identified by “Hrsg.” highlighted at the end. In the case of multiple authors, these are separated from + each other by slashes.
Are on one website If no authors are mentioned, the name of the website or the publishing + institution takes the place of the author.'; +$string['editlicensesformfilelicense_help'] = 'Please indicate which license the material used is under.'; +$string['editlicensesformfileurl'] = 'Source or URL'; +$string['editlicensesformfileurl_help'] = 'Where was the work published?

For books, the publisher is + responsible and the place of publication, for magazine articles the name of the magazine plus the year, volume, issue + and page details To provide websites with the full URL.'; +$string['elements:manage'] = 'Manage components'; +$string['elements:showteachercomponents'] = 'Show components that are for teacher use only'; +$string['elements:viewplugin'] = 'View Elements plugin'; +$string['enablepreview'] = 'Enable preview'; +$string['enablepreview_desc'] = 'If enabled, a preview is showed when you hover the mouse cursor over each component.'; +$string['error_export'] = 'Error creating export file'; +$string['error_fileimport'] = 'Error importing file "{$a}"'; +$string['error_import_component'] = 'Error importing component "{$a}"'; +$string['error_import_missing_table'] = 'Error while importing xml: missing table "{$a}"'; +$string['errorbackupfile'] = 'Error in backup file'; +$string['errorcompcat'] = '**compcat** cant be empty'; +$string['errordisplayname'] = 'Displayname cant be empty'; +$string['errorname'] = 'Name cant be empty'; +$string['export'] = 'Export'; +$string['files'] = 'Files'; +$string['flavor'] = 'Flavor'; +$string['flavors'] = 'Flavors'; +$string['generalsettings'] = 'General settings'; +$string['hideforstudents'] = 'Hide for students'; +$string['iconurl'] = 'Icon URL'; +$string['import'] = 'Import'; +$string['import_simulation'] = 'Import simulation'; +$string['js'] = 'JS'; +$string['linktomanagerdesc'] = 'Go to management page to edit categories, components, flavors and variants.'; +$string['linktomanagername'] = 'Link to management'; +$string['manage'] = 'Manage'; +$string['management'] = 'Management'; +$string['menuitem_elements'] = 'Course elements'; +$string['name'] = 'Name'; +$string['newcategory'] = 'New category "{$a}"'; +$string['newcompflavor'] = 'Create relation component<->flavor "{$a}"'; +$string['newcomponent'] = 'New component "{$a}"'; +$string['newcompvariant'] = 'Create relation component<->variant "{$a}"'; +$string['newfile'] = 'New file "{$a}"'; +$string['newflavor'] = 'New flavor "{$a}"'; +$string['newmetadata'] = 'New license "{$a}"'; +$string['newmetadatafilemissing'] = 'Related file not found: "{$a}"'; +$string['newvariant'] = 'New variant "{$a}"'; +$string['pluginname'] = 'Course elements'; +$string['preview'] = 'Preview'; +$string['previewcss'] = 'Preview CSS'; +$string['previewcsstext'] = 'If everything is correct, the component should be shown in all flavors'; +$string['previewdefault'] = 'Place the pointer on any component to see its preview.'; +$string['privacy:preference:category'] = 'Preferred category'; +$string['privacy:preference:components_flavors'] = 'Preferred flavor for each component'; +$string['privacy:preference:components_variants'] = 'Preferred variants of each component'; +$string['replacecategory'] = 'Replace category "{$a}"'; +$string['replacecompflavor'] = 'Replace relation component<->flavor "{$a}"'; +$string['replacecomponent'] = 'Replace component "{$a}"'; +$string['replacecompvariant'] = 'Replace relation component<->variant "{$a}"'; +$string['replacefile'] = 'Replace file "{$a}"'; +$string['replaceflavor'] = 'Replace flavor "{$a}"'; +$string['replacevariant'] = 'Replace variant "{$a}"'; +$string['showprinturls'] = 'Pick an icon'; +$string['text'] = 'Text'; +$string['unchangedfile'] = 'File "{$a}" is unchanged'; +$string['validclassname'] = 'The name has to be a valid css class name. It may only contain letters, numbers and the characters "-" and "_". It must start with a letter or "_". Using only lowercase letters is recommended.'; +$string['variant'] = 'Variant'; +$string['variants'] = 'Variants'; +$string['wipe'] = 'Wipe everything'; +$string['wipewarning'] = 'This will remove all categories, files, components, flavors and variants. There is no way to undo this step. Make sure, you have a backup!'; diff --git a/lib.php b/lib.php index 5e4bfcf..562f3b6 100644 --- a/lib.php +++ b/lib.php @@ -15,71 +15,107 @@ // along with Moodle. If not, see . /** - * Tiny C4L library functions. + * Tiny Elements library functions. * - * @package tiny_c4l + * @package tiny_elements * @copyright 2023 Marc Català * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -defined('MOODLE_INTERNAL') || die(); +use tiny_elements\local\utils; /** - * Return a list of all the user preferences used by tiny_c4l. + * Return a list of all the user preferences used by tiny_elements. * * @return array */ -function tiny_c4l_user_preferences() { +function tiny_elements_user_preferences() { $preferences = []; - $preferences['c4l_components_variants'] = [ - 'type' => PARAM_RAW, + $preferences['elements_category'] = [ + 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, - 'default' => '', + 'default' => 0, + ]; + + $preferences['elements_category_flavors'] = [ + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, + ]; + + $preferences['elements_component_variants'] = [ + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, ]; return $preferences; } /** - * Serves the tiny_c4l files. + * Serve the requested file for the tiny_elements plugin. * - * @param stdClass $course course object - * @param stdClass $cm course module object - * @param stdClass $context context object - * @param string $filearea file area - * @param array $args extra arguments + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param context $context the context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving - * @return bool false if file not found, does not return if found - just send the file + * @return bool false if the file not found, just send the file otherwise and do not return anything */ -function tiny_c4l_pluginfile($course, +function tiny_elements_pluginfile( + $course, $cm, $context, - $filearea, - $args, - $forcedownload, - array $options = []) { + string $filearea, + array $args, + bool $forcedownload, + array $options +): bool { + global $CFG; - $compicon = strpos($filearea, 'compicon') !== false; - $compimage = strpos($filearea, 'customimagesbank') !== false; + if ($CFG->forcelogin && !isloggedin()) { + send_file('', '', -1, 0, true); + } - if ($context->contextlevel == CONTEXT_SYSTEM && ($compicon || $compimage)) { - // Get file. - $fs = get_file_storage(); - $relativepath = implode('/', $args); - $fullpath = "/$context->id/tiny_c4l/$filearea/$relativepath"; - $file = $fs->get_file_by_hash(sha1($fullpath)); + if ($filearea === 'export') { + require_capability('tiny/elements:manage', $context); - if (!$file || $file->is_directory()) { - return false; + $exporter = new \tiny_elements\exporter($context->id); + + $compcatid = 0; + + if (count($args) > 1) { + $compcatid = (int)$args[1]; } - if (PHPUNIT_TEST) { - return $file; + $exportfile = $exporter->export($compcatid); + + send_stored_file($exportfile, 0, ['dontdie' => true]); + + $exportfile->delete(); + + die(); + } else if ($filearea === 'images') { + $fs = get_file_storage(); + $fullpath = '/1/tiny_elements/images/' . implode('/', $args); + if ((!$file = $fs->get_file_by_hash(sha1($fullpath))) || $file->is_directory()) { + return false; } - send_stored_file($file, null, 0, false, $options); - } else { + send_stored_file($file, 0, 0, $forcedownload, $options); + } else if ($filearea === 'js') { + $js = utils::get_js_from_cache(); + if (!$js) { + return send_file_not_found(); + } + send_file($js, 'tiny_elements_scripts.js', null, 0, true, false, 'text/javascript'); + return true; + } + $css = utils::get_css_from_cache(); + if (!$css) { send_file_not_found(); + return false; } + send_file($css, 'tiny_elements_styles.css', null, 0, true, false, 'text/css'); + return true; } diff --git a/management.php b/management.php new file mode 100644 index 0000000..bf12778 --- /dev/null +++ b/management.php @@ -0,0 +1,128 @@ +. + +/** + * Management site: create, import and edit components. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use tiny_elements\local\utils; +use tiny_elements\local\constants; + +require('../../../../../config.php'); + +require_login(); + +$url = new moodle_url('/lib/editor/tiny/plugins/elements/management.php', []); +$PAGE->set_url($url); +$PAGE->set_context(context_system::instance()); +$PAGE->set_heading(get_string('menuitem_elements', 'tiny_elements') . ' ' . get_string('management', 'tiny_elements')); +$compcatactive = optional_param('compcat', '', PARAM_ALPHANUMEXT); +$showbulkedit = optional_param('showbulkedit', false, PARAM_BOOL); + +require_capability('tiny/elements:manage', context_system::instance()); + +echo $OUTPUT->header(); + +// Get all elements components. +$dbcompcats = $DB->get_records('tiny_elements_compcat', [], 'displayorder'); +$dbflavor = $DB->get_records('tiny_elements_flavor', [], 'displayorder'); +$dbcompflavor = $DB->get_records('tiny_elements_comp_flavor'); +$dbcomponent = $DB->get_records('tiny_elements_component', [], 'displayorder'); +$dbvariant = $DB->get_records('tiny_elements_variant'); +$dbcompvariant = $DB->get_records('tiny_elements_comp_variant'); + +// Use array_values so mustache can parse it. +$compcats = array_values($dbcompcats); +$flavor = array_values($dbflavor); +$component = array_values($dbcomponent); +$variant = array_values($dbvariant); + +// Build component preview images for management. +foreach ($component as $key => $value) { + // Add corresponding flavors. + $flavorsarr = []; + $flavorexamplesarr = []; + foreach ($dbcompflavor as $val) { + if ($val->componentname == $value->name) { + array_push($flavorsarr, $val->flavorname); + if (!empty($val->iconurl)) { + array_push($flavorexamplesarr, utils::replace_pluginfile_urls($val->iconurl ?? '', true)); + } + } + } + $component[$key]->flavorsarr = $flavorsarr; + $component[$key]->hasflavors = !empty($flavorsarr); + if (empty($flavorexamplesarr)) { + if ($value->iconurl) { + $flavorexamplesarr = [utils::replace_pluginfile_urls($value->iconurl, true)]; + } + } + $component[$key]->flavorexamplesarr = $flavorexamplesarr; + // Keep only the first two entries. + if (count($component[$key]->flavorexamplesarr) > 2) { + $component[$key]->flavorexamplesarr = array_slice($component[$key]->flavorexamplesarr, 0, 2); + } +} + +// Add flavor previews. +foreach ($flavor as $key => $value) { + $flavor[$key]->hascomponents = false; + // Look for an example in comp_flavor. + foreach ($dbcompflavor as $val) { + if ($val->flavorname == $value->name) { + $flavor[$key]->hascomponents = true; + if (!empty($val->iconurl)) { + $flavor[$key]->example = utils::replace_pluginfile_urls($val->iconurl ?? '', true); + continue; + } + } + } +} + +// Use empty array to create an add item. +$addentry = []; +array_push($compcats, $addentry); +array_push($flavor, $addentry); +array_push($component, $addentry); +array_push($variant, $addentry); + +// Add exportlink. +$exportlink = \moodle_url::make_pluginfile_url( + SYSCONTEXTID, + 'tiny_elements', + 'export', + null, + '/', + constants::FILE_NAME_EXPORT +)->out(); + +$params = new \stdClass(); +$params->compcatactive = $compcatactive; +$PAGE->requires->js_call_amd('tiny_elements/management', 'init', [$params]); +echo($OUTPUT->render_from_template('tiny_elements/management', [ + 'compcats' => $compcats, + 'flavor' => $flavor, + 'component' => $component, + 'variant' => $variant, + 'exportlink' => $exportlink, + 'showbulkedit' => $showbulkedit, +])); +echo $OUTPUT->footer(); diff --git a/package.json b/package.json index 28f8fba..3263d0e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "c4l", + "name": "elements", "private": true, "description": "A plugin for the Moodle TinyMCE editor providing a set of visual components designed explicitly for Learning, based on the parent project componentsforlearning.org.", "scripts": { diff --git a/pix/c4l_allpurposecard_icon.svg b/pix/c4l_allpurposecard_icon.svg deleted file mode 100644 index f700515..0000000 --- a/pix/c4l_allpurposecard_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/c4l_attention.svg b/pix/c4l_attention.svg deleted file mode 100644 index 241787f..0000000 --- a/pix/c4l_attention.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/c4l_attention_icon.svg b/pix/c4l_attention_icon.svg deleted file mode 100644 index 4371a4f..0000000 --- a/pix/c4l_attention_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/pix/c4l_customcomponent_icon.svg b/pix/c4l_customcomponent_icon.svg deleted file mode 100644 index 8df1452..0000000 --- a/pix/c4l_customcomponent_icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/c4l_docard.svg b/pix/c4l_docard.svg deleted file mode 100644 index 9a82ed4..0000000 --- a/pix/c4l_docard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/c4l_dodontcards_icon.svg b/pix/c4l_dodontcards_icon.svg deleted file mode 100644 index bfadda0..0000000 --- a/pix/c4l_dodontcards_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/pix/c4l_dontcard.svg b/pix/c4l_dontcard.svg deleted file mode 100644 index e831b3a..0000000 --- a/pix/c4l_dontcard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/c4l_example_icon.svg b/pix/c4l_example_icon.svg deleted file mode 100644 index f0f3cc6..0000000 --- a/pix/c4l_example_icon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/pix/c4l_figure_icon.svg b/pix/c4l_figure_icon.svg deleted file mode 100644 index 832d63e..0000000 --- a/pix/c4l_figure_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/pix/c4l_figure_placeholder.svg b/pix/c4l_figure_placeholder.svg deleted file mode 100644 index 92c754e..0000000 --- a/pix/c4l_figure_placeholder.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/pix/c4l_inlinetag_icon.svg b/pix/c4l_inlinetag_icon.svg deleted file mode 100644 index 16b49b4..0000000 --- a/pix/c4l_inlinetag_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/c4l_learningoutcomes_icon.svg b/pix/c4l_learningoutcomes_icon.svg deleted file mode 100644 index 322607e..0000000 --- a/pix/c4l_learningoutcomes_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/c4l_learningoutcomes_list_item.svg b/pix/c4l_learningoutcomes_list_item.svg deleted file mode 100644 index 7565a26..0000000 --- a/pix/c4l_learningoutcomes_list_item.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/c4l_learningoutcomes_shadow.svg b/pix/c4l_learningoutcomes_shadow.svg deleted file mode 100644 index 1620474..0000000 --- a/pix/c4l_learningoutcomes_shadow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/c4l_multipurposecard_icon.svg b/pix/c4l_multipurposecard_icon.svg deleted file mode 100644 index fb9e5fd..0000000 --- a/pix/c4l_multipurposecard_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/c4l_preview_icon.svg b/pix/c4l_preview_icon.svg deleted file mode 100644 index dcd73a5..0000000 --- a/pix/c4l_preview_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - -]> \ No newline at end of file diff --git a/pix/c4l_proceduralcontext_icon.svg b/pix/c4l_proceduralcontext_icon.svg deleted file mode 100644 index bcf974c..0000000 --- a/pix/c4l_proceduralcontext_icon.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/pix/icon.svg b/pix/icon.svg index b41ad8c..6b2b506 100644 --- a/pix/icon.svg +++ b/pix/icon.svg @@ -1,5 +1,40 @@ - - - - + + + + + diff --git a/pix/noun_project_icons/c4l_duedate.svg b/pix/noun_project_icons/c4l_duedate.svg deleted file mode 100644 index ee6b05a..0000000 --- a/pix/noun_project_icons/c4l_duedate.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_duedate_icon.svg b/pix/noun_project_icons/c4l_duedate_icon.svg deleted file mode 100644 index a609ad2..0000000 --- a/pix/noun_project_icons/c4l_duedate_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/noun_project_icons/c4l_estimatedtime.svg b/pix/noun_project_icons/c4l_estimatedtime.svg deleted file mode 100644 index 9d0da86..0000000 --- a/pix/noun_project_icons/c4l_estimatedtime.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_estimatedtime_icon.svg b/pix/noun_project_icons/c4l_estimatedtime_icon.svg deleted file mode 100644 index 7fdb559..0000000 --- a/pix/noun_project_icons/c4l_estimatedtime_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/pix/noun_project_icons/c4l_expectedfeedback.svg b/pix/noun_project_icons/c4l_expectedfeedback.svg deleted file mode 100644 index fbbe9dc..0000000 --- a/pix/noun_project_icons/c4l_expectedfeedback.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_expectedfeedback_icon.svg b/pix/noun_project_icons/c4l_expectedfeedback_icon.svg deleted file mode 100644 index c542f3e..0000000 --- a/pix/noun_project_icons/c4l_expectedfeedback_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/pix/noun_project_icons/c4l_gradingvalue.svg b/pix/noun_project_icons/c4l_gradingvalue.svg deleted file mode 100644 index b6ed1f4..0000000 --- a/pix/noun_project_icons/c4l_gradingvalue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_gradingvalue_icon.svg b/pix/noun_project_icons/c4l_gradingvalue_icon.svg deleted file mode 100644 index 2c09c7c..0000000 --- a/pix/noun_project_icons/c4l_gradingvalue_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/noun_project_icons/c4l_keyconcept_icon.svg b/pix/noun_project_icons/c4l_keyconcept_icon.svg deleted file mode 100644 index 2a20f03..0000000 --- a/pix/noun_project_icons/c4l_keyconcept_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/pix/noun_project_icons/c4l_quote_close.svg b/pix/noun_project_icons/c4l_quote_close.svg deleted file mode 100644 index 7528c86..0000000 --- a/pix/noun_project_icons/c4l_quote_close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_quote_icon.svg b/pix/noun_project_icons/c4l_quote_icon.svg deleted file mode 100644 index eea54c9..0000000 --- a/pix/noun_project_icons/c4l_quote_icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/pix/noun_project_icons/c4l_quote_open.svg b/pix/noun_project_icons/c4l_quote_open.svg deleted file mode 100644 index ed75e79..0000000 --- a/pix/noun_project_icons/c4l_quote_open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_readingcontext_icon.svg b/pix/noun_project_icons/c4l_readingcontext_icon.svg deleted file mode 100644 index de7c8d1..0000000 --- a/pix/noun_project_icons/c4l_readingcontext_icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/noun_project_icons/c4l_reminder.svg b/pix/noun_project_icons/c4l_reminder.svg deleted file mode 100644 index 57fe88f..0000000 --- a/pix/noun_project_icons/c4l_reminder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_reminder_icon.svg b/pix/noun_project_icons/c4l_reminder_icon.svg deleted file mode 100644 index 2d726cc..0000000 --- a/pix/noun_project_icons/c4l_reminder_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/noun_project_icons/c4l_tag_icon.svg b/pix/noun_project_icons/c4l_tag_icon.svg deleted file mode 100644 index 8b45b55..0000000 --- a/pix/noun_project_icons/c4l_tag_icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/noun_project_icons/c4l_tip.svg b/pix/noun_project_icons/c4l_tip.svg deleted file mode 100644 index 2bfcf5f..0000000 --- a/pix/noun_project_icons/c4l_tip.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pix/noun_project_icons/c4l_tip_icon.svg b/pix/noun_project_icons/c4l_tip_icon.svg deleted file mode 100644 index 0ff94b7..0000000 --- a/pix/noun_project_icons/c4l_tip_icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/pix/variants/align-center-variant-off.svg b/pix/variants/align-center-variant-off.svg deleted file mode 100644 index 6f0f37c..0000000 --- a/pix/variants/align-center-variant-off.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/pix/variants/align-center-variant-on.svg b/pix/variants/align-center-variant-on.svg deleted file mode 100644 index d26ed28..0000000 --- a/pix/variants/align-center-variant-on.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/pix/variants/align-left-variant-off.svg b/pix/variants/align-left-variant-off.svg deleted file mode 100644 index 5d810c2..0000000 --- a/pix/variants/align-left-variant-off.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/variants/align-left-variant-on.svg b/pix/variants/align-left-variant-on.svg deleted file mode 100644 index d0df3d2..0000000 --- a/pix/variants/align-left-variant-on.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/variants/align-right-variant-off.svg b/pix/variants/align-right-variant-off.svg deleted file mode 100644 index 83c1ba9..0000000 --- a/pix/variants/align-right-variant-off.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/variants/align-right-variant-on.svg b/pix/variants/align-right-variant-on.svg deleted file mode 100644 index b455a03..0000000 --- a/pix/variants/align-right-variant-on.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/pix/variants/caption-variant-off.svg b/pix/variants/caption-variant-off.svg deleted file mode 100644 index 74f6349..0000000 --- a/pix/variants/caption-variant-off.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/pix/variants/caption-variant-on.svg b/pix/variants/caption-variant-on.svg deleted file mode 100644 index e436441..0000000 --- a/pix/variants/caption-variant-on.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/pix/variants/comfort-reading-variant-off.svg b/pix/variants/comfort-reading-variant-off.svg deleted file mode 100644 index 9ef8980..0000000 --- a/pix/variants/comfort-reading-variant-off.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/pix/variants/comfort-reading-variant-on.svg b/pix/variants/comfort-reading-variant-on.svg deleted file mode 100644 index 63ffdc1..0000000 --- a/pix/variants/comfort-reading-variant-on.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/pix/variants/dont-card-only-variant-off.svg b/pix/variants/dont-card-only-variant-off.svg deleted file mode 100644 index 4de8ff2..0000000 --- a/pix/variants/dont-card-only-variant-off.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/pix/variants/dont-card-only-variant-on.svg b/pix/variants/dont-card-only-variant-on.svg deleted file mode 100644 index 347b368..0000000 --- a/pix/variants/dont-card-only-variant-on.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/pix/variants/full-width-variant-off.svg b/pix/variants/full-width-variant-off.svg deleted file mode 100644 index 2c05ac2..0000000 --- a/pix/variants/full-width-variant-off.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/pix/variants/full-width-variant-on.svg b/pix/variants/full-width-variant-on.svg deleted file mode 100644 index 944ae43..0000000 --- a/pix/variants/full-width-variant-on.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/pix/variants/ordered-list-variant-off.svg b/pix/variants/ordered-list-variant-off.svg deleted file mode 100644 index 03bf3b1..0000000 --- a/pix/variants/ordered-list-variant-off.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/pix/variants/ordered-list-variant-on.svg b/pix/variants/ordered-list-variant-on.svg deleted file mode 100644 index 5f30528..0000000 --- a/pix/variants/ordered-list-variant-on.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/pix/variants/quote-variant-off.svg b/pix/variants/quote-variant-off.svg deleted file mode 100644 index a177537..0000000 --- a/pix/variants/quote-variant-off.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/pix/variants/quote-variant-on.svg b/pix/variants/quote-variant-on.svg deleted file mode 100644 index 4a6e029..0000000 --- a/pix/variants/quote-variant-on.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/preview.php b/preview.php new file mode 100644 index 0000000..30edf63 --- /dev/null +++ b/preview.php @@ -0,0 +1,62 @@ +. + +/** + * Creates a preview for elements components + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Tobias Garske + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require('../../../../../config.php'); + +require_login(); + +$url = new moodle_url('/lib/editor/tiny/plugins/elements/preview.php', []); +$PAGE->set_url($url); +$PAGE->set_context(context_system::instance()); +$PAGE->set_heading($SITE->fullname); +$PAGE->set_pagelayout('popup'); + +echo $OUTPUT->header(); + +$component = required_param('component', PARAM_ALPHANUMEXT); +$flavor = required_param('flavor', PARAM_ALPHANUMEXT); + +$componentdata = $DB->get_record('tiny_elements_component', ['name' => $component]); +$categorydata = $DB->get_record('tiny_elements_compcat', ['name' => $componentdata->categoryname]); +$flavordata = $DB->get_record('tiny_elements_flavor', ['name' => $flavor]); + +$variant = ''; +$varianthtml = ''; +$componentdata->code = str_replace('{{CATEGORY}}', 'elements-' . $categorydata->name, $componentdata->code); +$componentdata->code = str_replace('{{COMPONENT}}', 'elements-' . $component, $componentdata->code); +$componentdata->code = str_replace('{{FLAVOR}}', 'elements-' . $flavor . '-flavor', $componentdata->code); +$componentdata->code = str_replace('{{VARIANTS}}', $variant, $componentdata->code); +$componentdata->code = str_replace('{{VARIANTSHTML}}', $varianthtml, $componentdata->code); +$componentdata->code = str_replace('{{PLACEHOLDER}}', $componentdata->text ?? 'Lorem ipsum', $componentdata->code); + +$componentdata->code = tiny_elements\local\utils::replace_pluginfile_urls($componentdata->code, true); + +if (empty($flavordata)) { + echo str_replace('{{FLAVOR}}', '', $componentdata->code); +} else { + echo str_replace('{{FLAVOR}}', $flavordata->name, $componentdata->code); +} + +echo $OUTPUT->footer(); diff --git a/printurls.php b/printurls.php new file mode 100644 index 0000000..ef500fe --- /dev/null +++ b/printurls.php @@ -0,0 +1,64 @@ +. + +/** + * Generate a list of image URLs for the tiny_elements plugin. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require('../../../../../config.php'); + +require_login(); + +$url = new moodle_url('/lib/editor/tiny/plugins/elements/printurls.php', []); +$PAGE->set_url($url); +$PAGE->set_context(context_system::instance()); + +$categoryid = optional_param('categoryid', 0, PARAM_INT); + +require_capability('tiny/elements:manage', context_system::instance()); + +$PAGE->set_heading($SITE->fullname); +echo $OUTPUT->header(); + +$fs = get_file_storage(); +$files = $fs->get_area_files(SYSCONTEXTID, 'tiny_elements', 'images', (empty($categoryid) ? false : $categoryid)); +$processedfiles = []; +foreach ($files as $file) { + if ($file->is_directory()) { + continue; + } + $processedfiles[] = [ + 'id' => $file->get_id(), + 'name' => $file->get_filename(), + 'url' => moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename() + )->out(), + ]; +} + +echo($OUTPUT->render_from_template('tiny_elements/imageurls', ['imageurls' => $processedfiles])); + +echo $OUTPUT->footer(); diff --git a/scss/_admin.scss b/scss/_admin.scss new file mode 100644 index 0000000..1e63ca6 --- /dev/null +++ b/scss/_admin.scss @@ -0,0 +1,89 @@ +#page-lib-editor-tiny-plugins-elements-preview { + #page { + padding-top: 0; + margin-top: 0; + } +} + +/* Admin backend */ +#page-lib-editor-tiny-plugins-elements-management, +#page-lib-editor-tiny-plugins-elements-printurls { + .col.item { + min-width: 250px; + max-width: 250px; + &.addcontainer .card { + background-color: lightgrey; + } + } + + .tiny_elements_thumbnail { + width: 48px; + height: 48px; + } + + .preview.management { + display: flex; + justify-content: space-evenly; + width: 100%; + height: 3rem; + padding-bottom: .25rem; + & > div { + width: 45%; + .elements-button-text { + height: 100%; + } + .elements-button-text::before { + display: flex; + width: unset; + height: 100%; + } + } + & > div.more { + display: flex; + flex-direction: column; + justify-content: center; + width: 10%; + .elements-button-text::before { + all: unset; + content: '...'; + } + .elements-button-text { + word-wrap: unset; + } + } + } + + .compcat:hover .card { + border: 1px solid #787878; + } + .compcat.active .card { + border: 1px solid #0b5190; + } + + .card-body { + display: flex; + flex-direction: column; + padding-top: .75rem; + } + + .card-body > div:hover { + cursor: pointer; + & + a.edit { + color: #094478; + } + } + + form[data-formtype="tiny_elements_comp_flavor"] input.form-control { + width: 100%; + } + + .compcat[data-compcat="found-items"] a { + display: none; + } + + form[data-formtype="tiny_elements_editlicense"] { + div[data-fieldtype="static"] { + padding-top: calc(0.75rem + 1px); + } + } +} diff --git a/scss/_buttons.scss b/scss/_buttons.scss index 28a58f2..786e223 100644 --- a/scss/_buttons.scss +++ b/scss/_buttons.scss @@ -1,86 +1,55 @@ /* PLUGIN UI STYLES */ -.c4l-plugin-container { - display: block; - padding: 1rem; - background-color: #f6f7f8; - border: 1px solid #dee2e6; -} - -.c4l-buttons-filters { - display: none; -} - -.c4l-select-filters { - display: grid; - grid-auto-flow: column; - width: 100%; - margin: 7px 0 36px 0; -} - -.c4l-select-filter { - grid-column: 1 / -1; - background-color: #fff; - border: 1px solid #dfe2e5; - border-radius: 7px; - padding: 17px; - width: 100%; - text-transform: capitalize; - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; - background-image: url([[pix:t/dropdown]]); - background-repeat: no-repeat; - background-position-x: 95%; - background-position-y: center; -} - -.c4l-button-filter { - background-color: #fff; - border: 1px solid #dfe2e5; - padding: 10px 16px; - font-weight: 500; - font-size: 10.5px; - line-height: 12px; - letter-spacing: 0.05em; - color: #424b62; - text-transform: uppercase; -} - -.c4l-button-filter:hover, -.c4l-button-filter.c4l-button-filter-enabled { - background-color: #e7ebef; -} - -.c4l-button-filter:first-child { - border-radius: 6px 0 0 6px; -} - -.c4l-button-filter:last-child { - border-radius: 0 6px 6px 0; -} - -.c4l-buttons-preview { +.elements-plugin-container { + .nav.nav-tabs { + flex-wrap: wrap; + .nav-item a.active { + background-color: #f6f7f8; + border-bottom: 1px solid #f6f7f8; + &.dropdown-item { + color: inherit; + } + } + &, + .dropdown-item { + font-size: .8rem; + } + .nav-link { + border: 1px solid #d3d3d3; + border-bottom: none; + border-top-right-radius: .2rem; + border-top-left-radius: .2rem; + } + } +} + +.elements-buttons-preview { display: flex; justify-content: space-between; width: 100%; - max-height: 340px; + min-height: 31vh; + max-height: 60vh; + background-color: #f6f7f8; } -.c4l-buttons-grid { - display: block; +.elements-buttons-grid { + display: flex; + flex-wrap: wrap; + justify-content: start; + row-gap: 12px; + column-gap: 12px; margin-bottom: 0; width: 100%; overflow-y: auto; } -.c4l-hidden { +.elements-hidden { display: none !important; max-height: 0; padding: 0; } -.c4lt-dialog-button { +.elementst-dialog-button { display: flex; flex-direction: row; align-items: center; @@ -100,7 +69,7 @@ margin-bottom: 8px; } -.c4lt-dialog-button.c4l-custom-icon .c4l-button-text i { +.elementst-dialog-button.elements-custom-icon .elements-button-text i { position: absolute; background-repeat: no-repeat; background-size: contain; @@ -112,31 +81,33 @@ border-right: 1px solid #dce3f0; } -.c4lt-dialog-button .c4l-button-text { +.elementst-dialog-button .elements-button-text { display: flex; align-items: center; height: 100%; } -.c4lt-dialog-button .c4l-button-text::before { +.elementst-dialog-button .elements-button-text::before { position: absolute; - top: 0; left: 0; height: 100%; min-height: 23px; - width: 49px; display: flex; justify-content: center; align-items: center; border-right: 1px solid #dce3f0; + width: 60px; + height: 60px; + top: 3px; + align-content: center; } -.c4lt-dialog-button:hover, -.c4lt-dialog-button:active { +.elementst-dialog-button:hover, +.elementst-dialog-button:active { box-shadow: inset 0 0 3px rgba(22, 121, 249, 0.3); } -.c4lt-dialog-button .c4l-button-text { +.elementst-dialog-button .elements-button-text { position: relative; font-size: 12px; font-weight: 500; @@ -145,174 +116,19 @@ padding-left: 62px; } -.c4l-code-preview { +.elements-code-preview { display: none; overflow: hidden; background-color: #fff; + border-top: 2px solid #f6f7f8; } -.c4l-code-preview .c4l-component-code { +.elements-code-preview .elements-component-code { -webkit-transform: scale(0.6); -ms-transform: scale(0.6); transform: scale(0.6); width: 160%; margin-top: 35px; + position: absolute; } -.c4l-code-preview .c4l-component-code .c4l-learningoutcomes { - -webkit-transform: translateY(-15px); - -ms-transform: translateY(-15px); - transform: translateY(-15px); -} - -/* Contextual components buttons */ - -.c4l-keyconcept-icon::before { - content: ''; -} - -.c4l-keyconcept-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_keyconcept_icon]]); -} - -.c4l-tip-icon::before { - content: ''; -} - -.c4l-tip-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_tip_icon]]); -} - -.c4l-reminder-icon::before { - content: ''; -} - -.c4l-reminder-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_reminder_icon]]); -} - -.c4l-attention-icon::before { - content: ''; -} - -.c4l-attention-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_attention_icon]]); -} - -.c4l-quote-icon::before { - content: ''; -} - -.c4l-quote-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_quote_icon]]); -} - -.c4l-dodontcards-icon::before { - content: ''; -} - -.c4l-dodontcards-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_dodontcards_icon]]); -} - -.c4l-readingcontext-icon::before { - content: ''; -} - -.c4l-readingcontext-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_readingcontext_icon]]); -} - -.c4l-example-icon::before { - content: ''; -} - -.c4l-example-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_example_icon]]); -} - -.c4l-figure-icon::before { - content: ''; -} - -.c4l-figure-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_figure_icon]]); -} - -.c4l-tag-icon::before { - content: ''; -} - -.c4l-tag-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_tag_icon]]); -} - -/* Procedural components buttons */ - -.c4l-estimatedtime-icon::before { - content: ''; -} - -.c4l-estimatedtime-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_estimatedtime_icon]]); -} - -.c4l-duedate-icon::before { - content: ''; -} - -.c4l-duedate-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_duedate_icon]]); -} - -.c4l-proceduralcontext-icon::before { - content: ''; -} - -.c4l-proceduralcontext-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_proceduralcontext_icon]]); -} - -.c4l-learningoutcomes-icon::before { - content: ''; -} - -.c4l-learningoutcomes-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_learningoutcomes_icon]]); -} - -/* Evaluative components buttons */ - -.c4l-gradingvalue-icon::before { - content: ''; -} - -.c4l-gradingvalue-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_gradingvalue_icon]]); -} - -.c4l-expectedfeedback-icon::before { - content: ''; -} - -.c4l-expectedfeedback-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_expectedfeedback_icon]]); -} - -/* Helper components buttons */ - -.c4l-allpurposecard-icon::before { - content: ''; -} - -.c4l-allpurposecard-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_allpurposecard_icon]]); -} - -.c4l-inlinetag-icon::before { - content: ''; -} - -.c4l-inlinetag-icon .c4l-button-text::before { - content: url([[pix:tiny_c4l|c4l_inlinetag_icon]]); -} diff --git a/scss/_buttons_variants.scss b/scss/_buttons_variants.scss index 12d9689..db0c433 100644 --- a/scss/_buttons_variants.scss +++ b/scss/_buttons_variants.scss @@ -1,6 +1,6 @@ /* BUTTON VARIANTS */ -.c4lt-dialog-button .c4l-button-variants { +.elementst-dialog-button .elements-button-variants { display: grid; grid-auto-flow: column; grid-gap: 3px; @@ -8,112 +8,32 @@ width: 100%; height: 42px; margin-right: 3px; -} - -.c4lt-dialog-button .c4l-button-variants .c4l-button-variant { - display: flex; - justify-content: center; - align-items: center; - background-color: #ecf3ff; - color: #1679f9; - height: 42px; - width: 36px; -} - -.c4lt-dialog-button .c4l-button-variants .c4l-button-variant.on, -.c4lt-dialog-button .c4l-button-variants .c4l-button-variant:hover { - color: #fff; - background-color: #1679f9; -} - -/* BUTTON VARIANTS ICON */ - -.c4l-button-variant.align-center-variant-off::before { - content: url([[pix:tiny_c4l|variants/align-center-variant-off]]); -} - -.c4l-button-variant.align-center-variant-on::before, -.c4l-button-variant.align-center-variant-on:hover::before, -.c4l-button-variant.align-center-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/align-center-variant-on]]); -} - -.c4l-button-variant.align-left-variant-off::before { - content: url([[pix:tiny_c4l|variants/align-left-variant-off]]); -} - -.c4l-button-variant.align-left-variant-on::before, -.c4l-button-variant.align-left-variant-on:hover::before, -.c4l-button-variant.align-left-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/align-left-variant-on]]); -} - -.c4l-button-variant.align-right-variant-off::before { - content: url([[pix:tiny_c4l|variants/align-right-variant-off]]); -} - -.c4l-button-variant.align-right-variant-on::before, -.c4l-button-variant.align-right-variant-on:hover::before, -.c4l-button-variant.align-right-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/align-right-variant-on]]); -} - -.c4l-button-variant.caption-variant-off::before { - content: url([[pix:tiny_c4l|variants/caption-variant-off]]); -} - -.c4l-button-variant.caption-variant-on::before, -.c4l-button-variant.caption-variant-on:hover::before, -.c4l-button-variant.caption-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/caption-variant-on]]); -} - -.c4l-button-variant.comfort-reading-variant-off::before { - content: url([[pix:tiny_c4l|variants/comfort-reading-variant-off]]); -} - -.c4l-button-variant.comfort-reading-variant-on::before, -.c4l-button-variant.comfort-reading-variant-on:hover::before, -.c4l-button-variant.comfort-reading-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/comfort-reading-variant-on]]); -} - -.c4l-button-variant.dont-card-only-variant-off::before { - content: url([[pix:tiny_c4l|variants/dont-card-only-variant-off]]); -} - -.c4l-button-variant.dont-card-only-variant-on::before, -.c4l-button-variant.dont-card-only-variant-on:hover::before, -.c4l-button-variant.dont-card-only-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/dont-card-only-variant-on]]); -} - -.c4l-button-variant.full-width-variant-off::before { - content: url([[pix:tiny_c4l|variants/full-width-variant-off]]); -} - -.c4l-button-variant.full-width-variant-on::before, -.c4l-button-variant.full-width-variant-on:hover::before, -.c4l-button-variant.full-width-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/full-width-variant-on]]); -} - -.c4l-button-variant.ordered-list-variant-off::before { - content: url([[pix:tiny_c4l|variants/ordered-list-variant-off]]); -} - -.c4l-button-variant.ordered-list-variant-on::before, -.c4l-button-variant.ordered-list-variant-on:hover::before, -.c4l-button-variant.ordered-list-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/ordered-list-variant-on]]); -} - -.c4l-button-variant.quote-variant-off::before { - content: url([[pix:tiny_c4l|variants/quote-variant-off]]); -} -.c4l-button-variant.quote-variant-on::before, -.c4l-button-variant.quote-variant-on:hover::before, -.c4l-button-variant.quote-variant-off:hover::before { - content: url([[pix:tiny_c4l|variants/quote-variant-on]]); + .elements-button-variant { + display: flex; + justify-content: center; + align-items: center; + background-color: #ecf3ff; + color: #1679f9; + height: 42px; + width: 36px; + + &::before { + content: ' '; + background-size: 50%; + background-repeat: no-repeat; + background-position: center; + height: 42px; + width: 36px; + } + + &.on, &:hover { + background-color: #1679f9; + color: #fff; + + &::before { + filter: brightness(10); + } + } + } } diff --git a/scss/_components.scss b/scss/_components.scss deleted file mode 100644 index 3f305a5..0000000 --- a/scss/_components.scss +++ /dev/null @@ -1,771 +0,0 @@ -/* COMPONENT STYLES */ - -/* .c4lv prefix instead of .c4l only for the parent class of an element supporting variants */ - -/* Utilities */ - -.c4l-spacer { - display: block; - height: 12px; - - + .c4l-keyconcept, - + .c4lv-keyconcept, - + .c4l-tip, - + .c4lv-tip, - + .c4l-reminder, - + .c4lv-reminder, - + .c4l-attention, - + .c4lv-attention, - + .c4l-quote, - + .c4lv-quote, - + .c4l-example, - + .c4lv-example, - + .c4l-expectedfeedback, - + .c4lv-expectedfeedback, - + .c4l-learningoutcomes, - + .c4lv-learningoutcomes { - margin: 0 auto; - } - - + .c4l-dodontcards .c4l-dodontcards-do, - + .c4lv-dodontcards .c4l-dodontcards-do { - margin-top: 0; - - } - - + .c4l-dodontcards .c4l-dodontcards-dont, - + .c4lv-dodontcards .c4l-dodontcards-dont { - margin-bottom: 0; - } - - + .c4l-readingcontext, - + .c4lv-readingcontext { - margin-top: 16px; - margin-bottom: 4px; - } - - + .c4l-figure, - + .c4lv-figure { - margin: 24px auto 14px auto; - } - - + .c4l-proceduralcontext, - + .c4lv-proceduralcontext { - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - } -} - -.c4l-inline-group { - display: flex; - flex-direction: row; - align-content: flex-end; - justify-content: flex-end; -} - -.c4l-display-left { - display: flex; - flex-direction: row; - align-content: flex-start; - justify-content: flex-start; -} - -.c4l-spacer + .c4l-display-left { - margin-top: 0; -} - -.c4l-embedded-caption { - font-size: 13px; - margin-top: 12px; - text-align: right; - - span { - text-transform: uppercase; - font-size: 14px; - } - - span::after { - content: ", "; - } -} - -/* Key concept */ - -.c4l-keyconcept, -.c4lv-keyconcept { - min-width: 200px; - max-width: 99%; - background-color: #f1f5fe; - padding: 24px 36px 30px 36px; - border: none; - border-left: 6px solid #387af1; - margin: 36px auto; - position: relative; - border-radius: 0; - - p:last-of-type { - margin-bottom: 0; - } -} - -/* Tip */ - -.c4l-tip, -.c4lv-tip { - min-width: 200px; - max-width: 99%; - background-color: #fbeffa; - padding: 24px 48px 30px 36px; - border: none; - border-left: 6px solid #b00ca9; - margin: 36px auto; - position: relative; - border-radius: 0; - - p:last-of-type { - margin-bottom: 0; - } - - &::after { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_tip]]); - position: absolute; - top: 6px; - right: -3px; - } -} - -/* Reminder */ - -.c4l-reminder, -.c4lv-reminder { - min-width: 200px; - max-width: 99%; - background-color: #eff8fd; - padding: 24px 48px 30px 36px; - border: none; - border-left: 6px solid #16b9ff; - margin: 36px auto; - position: relative; - border-radius: 0; - - p:last-of-type { - margin-bottom: 0; - } - - &::after { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_reminder]]); - position: absolute; - top: 6px; - right: -3px; - } -} - -/* Attention */ - -.c4l-attention, -.c4lv-attention { - min-width: 200px; - max-width: 99%; - background-color: #fef6ed; - padding: 24px 48px 30px 36px; - border: none; - border-left: 6px solid #f88923; - margin: 36px auto; - position: relative; - border-radius: 0; - - p:last-of-type { - margin-bottom: 0; - } - - &::after { - content: url([[pix:tiny_c4l|c4l_attention]]); - position: absolute; - top: 6px; - right: -3px; - } -} - -/* Quote */ - -.c4l-quote, -.c4lv-quote { - /*font-family: "Lora", serif; <== Reccomended font -not included: requires link to Google Fonts */ - font-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, - Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, - Segoe UI Emoji, Segoe UI Symbol; - font-size: 16px; - line-height: 24px; - margin: 24px auto; - min-width: 200px; - max-width: 100%; - - .c4l-quote-body { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-start; - align-items: stretch; - align-content: space-between; - font-style: italic; - } - - .c4l-quote-line { - border-left: 4px solid #387af1; - margin-right: 16px; - margin-top: 4px; - margin-bottom: 2px; - } - - .c4l-quote-text::before { - content: ""; - position: static; - top: 0; - margin-right: 0; - } - - .c4l-quote-text::after { - content: ""; - position: static; - top: 0; - margin-left: 0; - } - - .c4l-quote-text p:first-of-type::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_quote_open]]); - position: relative; - top: -4px; - margin-right: 2px; - } - - .c4l-quote-text p:last-of-type::after { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_quote_close]]); - position: relative; - top: -2px; - margin-left: 2px; - } - - .c4l-quote-text p:last-of-type { - margin-bottom: 0; - } -} - -.c4l-quote { - .c4l-quote-caption { - font-size: 13px; - margin-top: 12px; - text-align: right; - } - - .c4l-quote-caption span { - text-transform: uppercase; - font-size: 14px; - } - - .c4l-quote-caption span::after { - content: ", "; - } -} - -/* Do - Don't cards */ - -.c4l-dodontcards, -.c4lv-dodontcards { - .c4l-dodontcards-do, - .c4l-dodontcards-do { - margin-top: 36px; - } - - .c4l-dodontcards-dont, - .c4l-dodontcards-dont { - margin-bottom: 36px; - } - - > .c4l-dodontcards-dont { - margin-top: 12px; - } - - .c4l-dodontcards-do { - min-width: 200px; - max-width: 100%; - background: #f1fbf5; - border-radius: 10px; - padding: 24px 48px 30px 36px; - margin: 12px auto; - position: relative; - } - - .c4l-dodontcards-do::before { - content: url([[pix:tiny_c4l|c4l_docard]]); - position: absolute; - top: 12px; - right: 12px; - } - - .c4l-dodontcards-dont { - min-width: 200px; - max-width: 100%; - background: #ffefef; - border-radius: 10px; - padding: 24px 48px 30px 36px; - margin: 12px auto; - position: relative; - } - - .c4l-dodontcards-dont::before { - content: url([[pix:tiny_c4l|c4l_dontcard]]); - position: absolute; - top: 12px; - right: 12px; - } - - .c4l-dodontcards-do p, - .c4l-dodontcards-dont p { - margin-bottom: 6px; - } - - .c4l-dodontcards-do p:last-of-type, - .c4l-dodontcards-dont p:last-of-type { - margin-bottom: 0; - } -} - -/* Reading Context */ - -.c4l-readingcontext, -.c4lv-readingcontext { - min-width: 200px; - max-width: 75%; - background-color: #fff; - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); - box-sizing: border-box; - margin: 36px auto; - - p { - font-size: 16px; - line-height: 23px; - } -} - -.c4l-readingcontext { - padding: 30px 40px 19px 40px; - font-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, - Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, - Segoe UI Emoji, Segoe UI Symbol; - - .c4l-readingcontext-caption { - font-size: 16px; - margin-top: 24px; - text-align: right; - padding-bottom: 14px; - font-style: italic; - } - - .c4l-readingcontext-caption span { - text-transform: uppercase; - font-size: 16px; - font-style: normal; - } - - .c4l-readingcontext-caption span::after { - content: ", "; - } -} - -.c4lv-readingcontext { - padding: 30px 40px 32px 40px; - font-family: sans-serif; - - p:last-of-type { - margin-bottom: 0; - } - - .c4l-embedded-caption { - margin-top: 1rem; - font-size: 16px; - font-style: italic; - } - - &.c4l-comfort-reading-variant { - font-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, - Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, - Segoe UI Emoji, Segoe UI Symbol; - } -} - -/* Example */ - -.c4l-example, -.c4lv-example { - font-size: 15px; - line-height: 22px; - color: inherit; - background: #fff; - border-radius: 0; - margin: 36px auto; - min-width: 75%; - max-width: 100%; - padding: 18px 24px; - position: relative; - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.13); - - h1 { - font-weight: 700; - font-size: 11px; - line-height: 21px; - letter-spacing: 0.5px; - color: #3171e3; - margin: 0 0 24px 0; - text-transform: uppercase; - font-family: inherit; - display: inline-block; - border-bottom: 2px solid #3171e3; - } -} - -/* Figure */ - -.c4l-figure, -.c4lv-figure { - min-width: 200px; - margin: 48px auto; - - img { - width: 100%; - &:not([src]), - &[src=""] { - content: url([[pix:tiny_c4l|c4l_figure_placeholder]]); - } - } - - figcaption { - font-size: 13px; - line-height: 16px; - color: #686d79; - margin-top: 7px; - } - - .c4l-figure-footer::after { - content: " | "; - font-weight: normal; - font-style: normal; - } - - .c4l-figure-caption { - font-style: normal; - font-size: 12px; - } - - figcaption span strong { - font-weight: 700; - } -} - -.c4l-figure { - max-width: 100%; -} - -.c4lv-figure { - max-width: 75%; -} - -/* Tag */ - -.c4l-tag, -.c4lv-tag { - display: inline-block; - font-size: 10px; - font-weight: 700; - color: #2167cf; - background-color: #f1f5fe; - border-radius: 30px; - padding: 4px 17px; - line-height: 20px; - margin-bottom: 24px; - text-transform: uppercase; - letter-spacing: 0.2px; -} - -.c4lv-tag.c4l-align-right-variant { - margin-left: auto; -} - -/* Estimated Time */ - -.c4l-estimatedtime, -.c4lv-estimatedtime { - font-size: 12px; - color: #2167cf; - background-color: #f1f5fe; - padding: 6px 14px; - font-weight: 700; - margin-left: 6px; - margin-bottom: 24px; - padding-top: 5px; - padding-bottom: 5px; - padding-left: 36px; - position: relative; - border-radius: 5px; - - span { - font-weight: 600; - font-size: 10px; - } - - &::before { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_estimatedtime]]); - position: absolute; - left: 12px; - } -} - -.c4lv-estimatedtime.c4l-align-left-variant { - margin-right: auto; - margin-left: 0; -} - -/* Due date */ - -.c4l-duedate, -.c4lv-duedate { - font-size: 12px; - color: #2167cf; - background-color: #f1f5fe; - padding: 6px 14px; - font-weight: 600; - margin-left: 6px; - margin-bottom: 24px; - padding-top: 5px; - padding-bottom: 5px; - padding-left: 36px; - position: relative; - border-radius: 5px; - - &::after { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_duedate]]); - position: absolute; - left: 12px; - } -} - -.c4lv-duedate.c4l-align-left-variant { - margin-right: auto; - margin-left: 0; -} - -/* Procedural Context */ - -.c4l-proceduralcontext, -.c4lv-proceduralcontext { - font-style: italic; - color: #3a56af; - margin-bottom: 12px; - padding-top: 24px; - padding-bottom: 24px; - font-weight: 400; -} - -/* Grading Value */ - -.c4l-gradingvalue, -.c4lv-gradingvalue { - font-size: 12px; - color: #2167cf; - background-color: #f1f5fe; - padding: 6px 14px; - font-weight: 700; - margin-left: 6px; - margin-bottom: 24px; - padding-top: 5px; - padding-bottom: 5px; - padding-left: 36px; - position: relative; - border-radius: 5px; - - span { - font-weight: 600; - font-size: 10px; - } - - &::after { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_gradingvalue]]); - position: absolute; - left: 12px; - top: 6px; - } -} - -.c4lv-gradingvalue.c4l-align-left-variant { - margin-right: auto; - margin-left: 0; -} - -/* Expected Feedback */ - -.c4l-expectedfeedback, -.c4lv-expectedfeedback { - min-width: 200px; - max-width: 90%; - background-color: #fff; - padding: 24px 36px 30px 36px; - margin: 36px auto; - font-style: italic; - position: relative; - border-radius: 8px; - border: none; - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.13); - - &::before { - content: ""; - } - - &::after { - content: url([[pix:tiny_c4l|noun_project_icons/c4l_expectedfeedback]]); - position: absolute; - bottom: 6px; - right: -3px; - } - - p:last-of-type { - margin-bottom: 0; - } -} - -/* All-purpose Card */ - -.c4l-allpurposecard, -.c4lv-allpurposecard { - min-width: 200px; - max-width: 100%; - background: #f1f5fe; - border-radius: 10px; - padding: 24px 48px 30px 36px; - margin: 24px auto; - position: relative; - - p { - margin-bottom: 6px; - } - - p:last-of-type { - margin-bottom: 0; - } -} - -.c4l-spacer + .c4l-allpurposecard { - margin: 0 auto; -} - -/* Inline Tag */ - -.c4l-inlinetag, -.c4lv-inlinetag { - font-weight: 900; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.2px; - display: inline-block; - color: #fff; - background: #3171e3; - border-radius: 7px; - padding: 2px 7px 1px 7px; - position: relative; - top: -2px; - margin-left: 4px; - margin-right: 4px; -} - -/* Learning Outcomes */ - -.c4l-learningoutcomes, -.c4lv-learningoutcomes { - min-width: 200px; - max-width: 99%; - background-color: #f2f5fd; - padding: 24px 48px 30px 36px; - border: none; - margin: 36px auto; - position: relative; - border-radius: 0; - - p:last-of-type { - margin-bottom: 0; - } - - .c4l-learningoutcomes-title { - position: relative; - top: -11px; - left: -39px; - padding: 6px 14px 5px; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - font-weight: 600; - font-size: 11px; - letter-spacing: 0.7px; - color: #fff; - background-color: #497ae9; - text-transform: uppercase; - font-family: inherit; - display: inline-block; - margin-top: 0; - filter: drop-shadow(0 1.55601px 3.11202px rgba(0, 0, 0, 7%)); - } - - .c4l-learningoutcomes-title::before { - content: url([[pix:tiny_c4l|c4l_learningoutcomes_shadow]]); - position: absolute; - width: 3px; - height: 2px; - top: 15.5px; - left: 0.5px; - } - - .c4l-learningoutcomes-list { - margin-top: 18px; - margin-bottom: 18px; - padding-left: 32px; - } - - .c4l-learningoutcomes-list > li { - position: relative; - } - - .c4l-learningoutcomes-list > li:not(:last-child) { - margin-bottom: 21px; - } - - .c4l-learningoutcomes-list li::before { - background-image: url([[pix:tiny_c4l|c4l_learningoutcomes_list_item]]); - background-size: 9px 11px; - display: inline-block; - width: 9px; - height: 11px; - content: ""; - justify-self: center; - position: absolute; - left: -32px; - top: 7px; - } - - .c4l-learningoutcomes-list li::marker { - color: transparent; - } -} - -.c4lv-learningoutcomes.c4l-ordered-list-variant { - .c4l-learningoutcomes-list { - counter-reset: section; - } - - .c4l-learningoutcomes-list li::before { - background-image: none; - position: absolute; - left: -32px; - top: 0; - counter-increment: section; - content: counter(section) ". "; - font-weight: 700; - color: #497ae9; - } -} diff --git a/scss/_overrides.scss b/scss/_overrides.scss index 46c73a8..77c435b 100644 --- a/scss/_overrides.scss +++ b/scss/_overrides.scss @@ -1,5 +1,5 @@ body.mce-content-body, -.c4l-code-preview { +.elements-code-preview { .collapse:not(.show) { display: block !important; } diff --git a/scss/_responsive.scss b/scss/_responsive.scss index 203bc6b..83eeeee 100644 --- a/scss/_responsive.scss +++ b/scss/_responsive.scss @@ -1,218 +1,48 @@ /* Media queries */ @media only screen and (min-width: 576px) { - .c4l-modal.modal-open .modal-dialog, - .c4l-modal-no-preview.modal-open .modal-dialog { + .elements-modal.modal-open .modal-dialog, + .elements-modal-no-preview.modal-open .modal-dialog { min-width: 550px; } - .c4l-select-filters { + .elements-select-filters { display: none; } - .c4l-buttons-filters { + .elements-buttons-filters, .elements-buttons-flavors { display: flex; justify-content: center; align-items: center; - margin: 16px 0 37px; - } - - .c4l-spacer { - + .c4l-keyconcept, - + .c4lv-keyconcept, - + .c4l-tip, - + .c4lv-tip, - + .c4l-reminder, - + .c4lv-reminder, - + .c4l-attention, - + .c4lv-attention, - + .c4l-expectedfeedback, - + .c4lv-expectedfeedback, - + .c4l-learningoutcomes, - + .c4lv-learningoutcomes { - margin: 24px auto 8px auto; - } - - + .c4l-quote, - + .c4lv-quote { - margin: 12px auto 0 auto; - } - - + .c4l-dodontcards .c4l-dodontcards-do, - + .c4lv-dodontcards .c4l-dodontcards-do { - margin-top: 0; - } - - + .c4l-dodontcards .c4l-dodontcards-dont, - + .c4lv-dodontcards .c4l-dodontcards-dont { - margin-bottom: 0; - } - - + .c4l-example, - + .c4lv-example { - margin: 12px auto 6px auto; - padding: 36px 48px; - } - } - - .c4l-embedded-caption { - margin-top: 24px; - } - - .c4l-quote, - .c4lv-quote { - margin: 36px auto; - max-width: 90%; - } - - .c4l-quote { - .c4l-quote-caption { - margin-top: 24px; - } - } - - .c4l-dodontcards, - .c4lv-dodontcards { - .c4l-dodontcards-do, - .c4l-dodontcards-dont { - max-width: 90%; - margin: 24px auto; - } - } - - .c4l-readingcontext, - .c4lv-readingcontext { - max-width: 88%; - } - - .c4l-example, - .c4lv-example { - max-width: 88%; - padding: 36px 48px; - } - - .c4l-expectedfeedback, - .c4lv-expectedfeedback { - max-width: 88%; - margin: 48px auto; - } - - .c4l-allpurposecard, - .c4lv-allpurposecard { - margin: 36px auto; - max-width: 90%; - } -} - -@media only screen and (min-width: 768px) { - .c4l-keyconcept, - .c4lv-keyconcept, - .c4l-tip, - .c4lv-tip, - .c4l-reminder, - .c4lv-reminder, - .c4l-attention, - .c4lv-attention, - .c4l-dodontcards-do, - .c4lv-dodontcards-do, - .c4l-dodontcards-dont, - .c4lv-dodontcards-dont, - .c4l-readingcontext, - .c4lv-readingcontext, - .c4l-expectedfeedback, - .c4lv-expectedfeedback, - .c4l-allpurposecard, - .c4lv-allpurposecard, - .c4l-learningoutcomes, - .c4lv-learningoutcomes { - max-width: 75%; - } - - .c4l-keyconcept.c4l-full-width-variant, - .c4lv-keyconcept.c4l-full-width-variant, - .c4l-tip.c4l-full-width-variant, - .c4lv-tip.c4l-full-width-variant, - .c4l-reminder.c4l-full-width-variant, - .c4lv-reminder.c4l-full-width-variant, - .c4l-attention.c4l-full-width-variant, - .c4lv-attention.c4l-full-width-variant, - .c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-do, - .c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-do, - .c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont, - .c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont, - .c4l-readingcontext.c4l-full-width-variant, - .c4lv-readingcontext.c4l-full-width-variant, - .c4l-expectedfeedback.c4l-full-width-variant, - .c4lv-expectedfeedback.c4l-full-width-variant, - .c4l-allpurposecard.c4l-full-width-variant, - .c4lv-allpurposecard.c4l-full-width-variant, - .c4l-learningoutcomes.c4l-full-width-variant, - .c4lv-learningoutcomes.c4l-full-width-variant, - .c4l-quote.c4l-full-width-variant, - .c4lv-quote.c4l-full-width-variant, - .c4l-example.c4l-full-width-variant, - .c4lv-example.c4l-full-width-variant { - max-width: 100%; - } - - .c4l-quote, - .c4lv-quote { - margin: 48px auto; - max-width: 75%; - } - - .c4l-spacer + .c4l-quote, - .c4l-spacer + .c4lv-quote { - margin: 24px auto 14px auto; - } - - .c4l-example, - .c4lv-example { - margin: 48px auto; - max-width: 75%; - } - - .c4l-spacer + .c4l-example, - .c4l-spacer + .c4lv-example { - margin: 24px auto 12px auto; + margin: 10px 0 10px; } } @media only screen and (min-width: 992px) { - .c4l-modal-no-preview.modal-open .modal-dialog { + .elements-modal-no-preview.modal-open .modal-dialog { max-width: 550px; } - .c4l-buttons-preview { - max-height: 314px; - } - - .c4l-buttons-preview.c4l-no-preview { + .elements-buttons-preview.elements-no-preview { justify-content: center; - max-height: 324px; } - .c4l-buttons-preview.c4l-no-preview .c4l-buttons-grid { + .elements-buttons-preview.elements-no-preview .elements-buttons-grid { grid-gap: 9px; justify-content: center; width: 405px; } - .c4l-buttons-preview.c4l-no-preview .c4l-code-preview { + .elements-buttons-preview.elements-no-preview .elements-code-preview { display: none; } - .c4l-buttons-grid { - display: grid; - grid-template-columns: repeat(auto-fill, 116px); - grid-template-rows: repeat(auto-fill, 100px); - grid-gap: 4px; - justify-content: flex-start; - width: 377px; - padding: 3px 0 3px 3px; + .elements-buttons-grid { + width: 400px; + padding: 10px; } - .c4lt-dialog-button.c4l-custom-icon .c4l-button-text i { + .elementst-dialog-button.elements-custom-icon .elements-button-text i { top: 20px; left: 0; right: 0; @@ -223,7 +53,7 @@ border-right: none; } - .c4lt-dialog-button { + .elementst-dialog-button { height: 100px; width: 116px; max-width: 116px; @@ -232,18 +62,23 @@ margin-bottom: 0; } - .c4lt-dialog-button .c4l-button-text::before { - top: 20px; + .elementst-dialog-button::before { + content: ''; + } + + .elementst-dialog-button .elements-button-text::before { left: 0; right: 0; - height: 20px; - width: 26px; margin: 0 auto; border-right: none; + display: inline-block; + width: 60px; + height: 60px; + top: 3px; } - .c4lt-dialog-button .c4l-button-text { - padding: 55px 8px 15px 8px; + .elementst-dialog-button .elements-button-text { + padding: 70px 8px 15px 8px; font-size: 12px; font-weight: 500; line-height: 13px; @@ -252,7 +87,7 @@ justify-content: center; } - .c4lt-dialog-button .c4l-button-variants { + .elementst-dialog-button .elements-button-variants { grid-auto-flow: row; grid-gap: 0; align-content: flex-start; @@ -263,7 +98,7 @@ margin: 0; } - .c4lt-dialog-button .c4l-button-variants .c4l-button-variant { + .elementst-dialog-button .elements-button-variants .elements-button-variant { display: flex; justify-content: center; align-items: center; @@ -272,31 +107,30 @@ width: 36px; } - .c4lt-dialog-button:hover, - .c4lt-dialog-button:active { + .elementst-dialog-button:hover, + .elementst-dialog-button:active { box-shadow: 0 0 0 3px rgba(22, 121, 249, 0.3); } - .c4lt-dialog-button .c4l-button-variants .c4l-button-variant:first-child, - .c4lt-dialog-button .c4l-button-variants .c4l-button-variant:nth-child(2) { + .elementst-dialog-button .elements-button-variants .elements-button-variant:first-child, + .elementst-dialog-button .elements-button-variants .elements-button-variant:nth-child(2) { border-bottom: 1px solid #fff; } - .c4lt-dialog-button:hover .c4l-button-variants .c4l-button-variant { + .elementst-dialog-button:hover .elements-button-variants .elements-button-variant { box-shadow: none; } - .c4l-code-preview { + .elements-code-preview { position: relative; display: flex; flex-direction: column; - height: 300px; - width: 343px; + width: 377px; align-items: center; justify-content: center; } - .c4l-preview-default { + .elements-preview-default { border: 1px solid #e1e5ee; border-radius: 8px; color: #9297a1; @@ -312,7 +146,7 @@ user-select: none; } - .c4l-text-preview { + .elements-text-preview { position: absolute; top: 10px; right: 10px; diff --git a/scss/_variants.scss b/scss/_variants.scss index 8a784fc..1d48470 100644 --- a/scss/_variants.scss +++ b/scss/_variants.scss @@ -2,60 +2,60 @@ /* Legacy */ -.c4l-keyconcept.c4l-full-width-variant, -.c4l-quote.c4l-full-width-variant, -.c4l-dodontcards.c4l-full-width-variant, -.c4l-dodontcards-do, -.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont, -.c4l-figure.c4l-full-width-variant, -.c4l-proceduralcontext.c4l-full-width-variant, -.c4l-learningoutcomes.c4l-full-width-variant, -.c4l-allpurposecard.c4l-full-width-variant { +.elements-keyconcept.elements-full-width-variant, +.elements-quote.elements-full-width-variant, +.elements-dodontcards.elements-full-width-variant, +.elements-dodontcards-do, +.elements-dodontcards.elements-full-width-variant .elements-dodontcards-dont, +.elements-figure.elements-full-width-variant, +.elements-proceduralcontext.elements-full-width-variant, +.elements-learningoutcomes.elements-full-width-variant, +.elements-allpurposecard.elements-full-width-variant { max-width: 100%; } -.c4l-tip.c4l-full-width-variant, -.c4l-reminder.c4l-full-width-variant, -.c4l-attention.c4l-full-width-variant { +.elements-tip.elements-full-width-variant, +.elements-reminder.elements-full-width-variant, +.elements-attention.elements-full-width-variant { /* 100% - 1% due to the outbound icon */ max-width: 99%; } -.c4l-readingcontext.c4l-full-width-variant, -.c4l-example.c4l-full-width-variant, -.c4l-expectedfeedback.c4l-full-width-variant { +.elements-readingcontext.elements-full-width-variant, +.elements-example.elements-full-width-variant, +.elements-expectedfeedback.elements-full-width-variant { /* 100% - 6% due to the shadow */ max-width: 94%; } /* Variants */ -/* .c4lv prefix instead of .c4l only for the parent class of an element supporting variants */ - -.c4lv-keyconcept.c4l-full-width-variant, -.c4lv-quote.c4l-full-width-variant, -.c4lv-dodontcards.c4l-full-width-variant, -.c4lv-dodontcards-do, -.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont, -.c4lv-figure.c4l-full-width-variant, -.c4lv-proceduralcontext.c4l-full-width-variant, -.c4lv-learningoutcomes.c4l-full-width-variant, -.c4lv-expectedfeedback.c4l-full-width-variant, -.c4lv-allpurposecard.c4l-full-width-variant, -.c4lv-custom-component.c4l-full-width-variant { +/* .elementsv prefix instead of .elements only for the parent class of an element supporting variants */ + +.elementsv-keyconcept.elements-full-width-variant, +.elementsv-quote.elements-full-width-variant, +.elementsv-dodontcards.elements-full-width-variant, +.elementsv-dodontcards-do, +.elementsv-dodontcards.elements-full-width-variant .elements-dodontcards-dont, +.elementsv-figure.elements-full-width-variant, +.elementsv-proceduralcontext.elements-full-width-variant, +.elementsv-learningoutcomes.elements-full-width-variant, +.elementsv-expectedfeedback.elements-full-width-variant, +.elementsv-allpurposecard.elements-full-width-variant, +.elementsv-custom-component.elements-full-width-variant { max-width: 100%; } -.c4lv-tip.c4l-full-width-variant, -.c4lv-reminder.c4l-full-width-variant, -.c4lv-attention.c4l-full-width-variant { +.elementsv-tip.elements-full-width-variant, +.elementsv-reminder.elements-full-width-variant, +.elementsv-attention.elements-full-width-variant { /* 100% - 1% due to the outbound icon */ max-width: 99%; } -.c4lv-readingcontext.c4l-full-width-variant, -.c4lv-example.c4l-full-width-variant, -.c4lv-expectedfeedback.c4l-full-width-variant { +.elementsv-readingcontext.elements-full-width-variant, +.elementsv-example.elements-full-width-variant, +.elementsv-expectedfeedback.elements-full-width-variant { /* 100% - 6% due to the shadow */ max-width: 94%; } diff --git a/scss/editor.scss b/scss/editor.scss index 11860af..d4e5903 100644 --- a/scss/editor.scss +++ b/scss/editor.scss @@ -1,4 +1,3 @@ -@import "components"; @import "overrides"; @import "responsive"; -@import "variants"; + diff --git a/scss/main.scss b/scss/main.scss index f455080..14b4bf3 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -1,6 +1,5 @@ +@import "admin"; @import "buttons"; @import "buttons_variants"; -@import "components"; @import "overrides"; @import "responsive"; -@import "variants"; diff --git a/settings.php b/settings.php index 0c0a0ac..5f0fdd0 100644 --- a/settings.php +++ b/settings.php @@ -15,162 +15,40 @@ // along with Moodle. If not, see . /** - * Tiny C4L plugin settings. + * Tiny Elements plugin settings. * - * @package tiny_c4l + * @package tiny_elements * @copyright 2022 Marc Català * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$ADMIN->add('editortiny', new admin_category('tiny_c4l', new lang_string('pluginname', 'tiny_c4l'))); +$ADMIN->add('editortiny', new admin_category('tiny_elements', new lang_string('pluginname', 'tiny_elements'))); -$settings = new admin_settingpage('tiny_c4l_settings', new lang_string('pluginname', 'tiny_c4l')); +$settings = new admin_settingpage('tiny_elements_settings', new lang_string('pluginname', 'tiny_elements')); if ($ADMIN->fulltree) { - // Custom components settings. - $settings->add(new admin_setting_heading('tiny_c4l/generalsettings', new lang_string('generalsettings', 'tiny_c4l'), '')); + $settings->add( + new admin_setting_heading('tiny_elements/generalsettings', new lang_string('generalsettings', 'tiny_elements'), '') + ); // Configure component preview. - $name = get_string('enablepreview', 'tiny_c4l'); - $desc = get_string('enablepreview_desc', 'tiny_c4l'); + $name = get_string('enablepreview', 'tiny_elements'); + $desc = get_string('enablepreview_desc', 'tiny_elements'); $default = 1; - $setting = new admin_setting_configcheckbox('tiny_c4l/enablepreview', $name, $desc, $default); - $settings->add($setting); - - // Configure available students' components. - $components = [ - 'keyconcept' => get_string('keyconcept', 'tiny_c4l'), - 'tip' => get_string('tip', 'tiny_c4l'), - 'reminder' => get_string('reminder', 'tiny_c4l'), - 'quote' => get_string('quote', 'tiny_c4l'), - 'dodontcards' => get_string('dodontcards', 'tiny_c4l'), - 'readingcontext' => get_string('readingcontext', 'tiny_c4l'), - 'example' => get_string('example', 'tiny_c4l'), - 'figure' => get_string('figure', 'tiny_c4l'), - 'tag' => get_string('tag', 'tiny_c4l'), - 'inlinetag' => get_string('inlinetag', 'tiny_c4l'), - 'attention' => get_string('attention', 'tiny_c4l'), - 'allpurposecard' => get_string('allpurposecard', 'tiny_c4l'), - ]; - $name = get_string('aimedatstudents', 'tiny_c4l'); - $desc = get_string('aimedatstudents_desc', 'tiny_c4l'); - $setting = new admin_setting_configmulticheckbox('tiny_c4l/aimedatstudents', $name, $desc, $components, $components); - $settings->add($setting); - - - // Configure not intended students' components. - $components = [ - 'estimatedtime' => get_string('estimatedtime', 'tiny_c4l'), - 'duedate' => get_string('duedate', 'tiny_c4l'), - 'proceduralcontext' => get_string('proceduralcontext', 'tiny_c4l'), - 'gradingvalue' => get_string('gradingvalue', 'tiny_c4l'), - 'expectedfeedback' => get_string('expectedfeedback', 'tiny_c4l'), - 'learningoutcomes' => get_string('learningoutcomes', 'tiny_c4l'), - ]; - $name = get_string('notintendedforstudents', 'tiny_c4l'); - $desc = get_string('notintendedforstudents_desc', 'tiny_c4l'); - $setting = new admin_setting_configmulticheckbox('tiny_c4l/notintendedforstudents', $name, $desc, [], $components); - $settings->add($setting); - - // Custom components settings. - $settings->add(new admin_setting_heading('tiny_c4l/customcomponents', new lang_string('customcomponents', 'tiny_c4l'), '')); - - // Number of custom components. - $name = 'tiny_c4l/customcompcount'; - $title = get_string('customcompcount', 'tiny_c4l'); - $description = get_string('customcompcountdesc', 'tiny_c4l'); - $options = range(0, 12); - $options = array_combine($options, $options); - $setting = new admin_setting_configselect($name, $title, $description, 0, $options); - $setting->set_updatedcallback('purge_all_caches'); - $settings->add($setting); - - - // CSS for preview content inside editor. - $name = 'tiny_c4l/custompreviewcss'; - $title = get_string('custompreviewcss', 'tiny_c4l'); - $url = new moodle_url('/admin/settings.php', ['section' => 'additionalhtml']); - $link = html_writer::link($url, get_string('additionalhtml', 'tiny_c4l'), ['target' => '_blank']); - $description = get_string('custompreviewcssdesc', 'tiny_c4l', $link); - $setting = new admin_setting_configtextarea($name, $title, $description, ''); + $setting = new admin_setting_configcheckbox('tiny_elements/enablepreview', $name, $desc, $default); $settings->add($setting); - // Images bank. - $fileareaid = 'customimagesbank'; - $name = 'tiny_c4l/customimagesbank'; - $title = get_string('customimagesbank', 'tiny_c4l'); - $description = get_string('customimagesbankdesc', 'tiny_c4l'); - $options = ['accepted_types' => ['image'], 'maxfiles' => -1]; - $setting = new admin_setting_configstoredfile($name, $title, $description, $fileareaid, 0, $options); - $settings->add($setting); - - // If we don't have any custom component yet, set to 0. - if (!$customcompcount = get_config('tiny_c4l', 'customcompcount')) { - $customcompcount = 0; - } - - for ($componentindex = 1; $componentindex <= $customcompcount; $componentindex++) { - - // Header. - $name = 'tiny_c4l/customcomptitle' . $componentindex; - $title = get_string('customcomptitle', 'tiny_c4l', $componentindex); - $title = html_writer::tag('h4', $title); - $setting = new admin_setting_description($name, '', $title); - $settings->add($setting); - - // Enable. - $name = 'tiny_c4l/customcompenable' . $componentindex; - $title = get_string('customcompenable', 'tiny_c4l', $componentindex); - $description = get_string('customcompenabledesc', 'tiny_c4l'); - $setting = new admin_setting_configcheckbox($name, $title, $description, 0); - $settings->add($setting); - - // Name. - $name = 'tiny_c4l/customcompname' . $componentindex; - $title = get_string('customcompname', 'tiny_c4l', $componentindex); - $description = get_string('customcompnamedesc', 'tiny_c4l'); - $setting = new admin_setting_configtext_with_maxlength($name, $title, $description, '', PARAM_TEXT, null, 15); - $settings->add($setting); - - // Icon. - $fileareaid = 'customcompicon' . $componentindex; - $name = 'tiny_c4l/customcompicon' . $componentindex; - $title = get_string('customcompicon', 'tiny_c4l', $componentindex); - $description = get_string('customcompicondesc', 'tiny_c4l'); - $options = ['accepted_types' => ['image'], 'maxfiles' => 1]; - $setting = new admin_setting_configstoredfile($name, $title, $description, $fileareaid, 0, $options); - $settings->add($setting); - - // Text. - $name = 'tiny_c4l/customcomptext' . $componentindex; - $title = get_string('customcomptext', 'tiny_c4l', $componentindex); - $description = get_string('customcomptextdesc', 'tiny_c4l'); - $setting = new admin_setting_configtextarea($name, $title, $description, '', PARAM_TEXT); - $settings->add($setting); - - // Code. - $name = 'tiny_c4l/customcompcode' . $componentindex; - $title = get_string('customcompcode', 'tiny_c4l', $componentindex); - $description = get_string('customcompcodedesc', 'tiny_c4l'); - $setting = new admin_setting_configtextarea($name, $title, $description, ''); - $settings->add($setting); - - // Variant. - $name = 'tiny_c4l/customcompvariant' . $componentindex; - $title = get_string('customcompvariant', 'tiny_c4l', $componentindex); - $description = get_string('customcompvariantdesc', 'tiny_c4l'); - $setting = new admin_setting_configcheckbox($name, $title, $description, 0); - $settings->add($setting); - - // Sortorder. - $name = 'tiny_c4l/customcompsortorder' . $componentindex; - $title = get_string('customcompsortorder', 'tiny_c4l', $componentindex); - $description = get_string('customcompsortorderdesc', 'tiny_c4l'); - $setting = new admin_setting_configtext($name, $title, $description, $componentindex, PARAM_INT); - $settings->add($setting); - } - + // Add text with link to management as setting. + $settings->add(new admin_setting_description( + 'tiny_elements/management', + get_string('linktomanagername', 'tiny_elements'), + get_string( + 'linktomanagerdesc', + 'tiny_elements', + (new moodle_url('/lib/editor/tiny/plugins/elements/management.php'))->out() + ) + )); } diff --git a/styles.css b/styles.css index d75eefe..b397ca0 100644 --- a/styles.css +++ b/styles.css @@ -1 +1 @@ -.c4l-plugin-container{display:block;padding:1rem;background-color:#f6f7f8;border:1px solid #dee2e6}.c4l-buttons-filters{display:none}.c4l-select-filters{display:grid;grid-auto-flow:column;width:100%;margin:7px 0 36px 0}.c4l-select-filter{grid-column:1/-1;background-color:#fff;border:1px solid #dfe2e5;border-radius:7px;padding:17px;width:100%;text-transform:capitalize;-moz-appearance:none;-webkit-appearance:none;appearance:none;background-image:url([[pix:t/dropdown]]);background-repeat:no-repeat;background-position-x:95%;background-position-y:center}.c4l-button-filter{background-color:#fff;border:1px solid #dfe2e5;padding:10px 16px;font-weight:500;font-size:10.5px;line-height:12px;letter-spacing:.05em;color:#424b62;text-transform:uppercase}.c4l-button-filter:hover,.c4l-button-filter.c4l-button-filter-enabled{background-color:#e7ebef}.c4l-button-filter:first-child{border-radius:6px 0 0 6px}.c4l-button-filter:last-child{border-radius:0 6px 6px 0}.c4l-buttons-preview{display:flex;justify-content:space-between;width:100%;max-height:340px}.c4l-buttons-grid{display:block;margin-bottom:0;width:100%;overflow-y:auto}.c4l-hidden{display:none !important;max-height:0;padding:0}.c4lt-dialog-button{display:flex;flex-direction:row;align-items:center;justify-content:space-between;position:relative;height:48px;max-height:100px;width:100%;max-width:100%;background-color:#fff;border:1px solid #e4e9ec;color:#535d76;box-shadow:0 0 6px 0 rgba(0,0,0,.1);overflow-wrap:anywhere;overflow:hidden;padding:0;margin-bottom:8px}.c4lt-dialog-button.c4l-custom-icon .c4l-button-text i{position:absolute;background-repeat:no-repeat;background-size:contain;background-position:center;min-height:23px;min-width:49px;top:auto;left:0;border-right:1px solid #dce3f0}.c4lt-dialog-button .c4l-button-text{display:flex;align-items:center;height:100%}.c4lt-dialog-button .c4l-button-text::before{position:absolute;top:0;left:0;height:100%;min-height:23px;width:49px;display:flex;justify-content:center;align-items:center;border-right:1px solid #dce3f0}.c4lt-dialog-button:hover,.c4lt-dialog-button:active{box-shadow:inset 0 0 3px rgba(22,121,249,.3)}.c4lt-dialog-button .c4l-button-text{position:relative;font-size:12px;font-weight:500;width:100%;text-align:left;padding-left:62px}.c4l-code-preview{display:none;overflow:hidden;background-color:#fff}.c4l-code-preview .c4l-component-code{-webkit-transform:scale(0.6);-ms-transform:scale(0.6);transform:scale(0.6);width:160%;margin-top:35px}.c4l-code-preview .c4l-component-code .c4l-learningoutcomes{-webkit-transform:translateY(-15px);-ms-transform:translateY(-15px);transform:translateY(-15px)}.c4l-keyconcept-icon::before{content:""}.c4l-keyconcept-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_keyconcept_icon]])}.c4l-tip-icon::before{content:""}.c4l-tip-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_tip_icon]])}.c4l-reminder-icon::before{content:""}.c4l-reminder-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_reminder_icon]])}.c4l-attention-icon::before{content:""}.c4l-attention-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_attention_icon]])}.c4l-quote-icon::before{content:""}.c4l-quote-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_quote_icon]])}.c4l-dodontcards-icon::before{content:""}.c4l-dodontcards-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_dodontcards_icon]])}.c4l-readingcontext-icon::before{content:""}.c4l-readingcontext-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_readingcontext_icon]])}.c4l-example-icon::before{content:""}.c4l-example-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_example_icon]])}.c4l-figure-icon::before{content:""}.c4l-figure-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_figure_icon]])}.c4l-tag-icon::before{content:""}.c4l-tag-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_tag_icon]])}.c4l-estimatedtime-icon::before{content:""}.c4l-estimatedtime-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_estimatedtime_icon]])}.c4l-duedate-icon::before{content:""}.c4l-duedate-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_duedate_icon]])}.c4l-proceduralcontext-icon::before{content:""}.c4l-proceduralcontext-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_proceduralcontext_icon]])}.c4l-learningoutcomes-icon::before{content:""}.c4l-learningoutcomes-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_learningoutcomes_icon]])}.c4l-gradingvalue-icon::before{content:""}.c4l-gradingvalue-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_gradingvalue_icon]])}.c4l-expectedfeedback-icon::before{content:""}.c4l-expectedfeedback-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_expectedfeedback_icon]])}.c4l-allpurposecard-icon::before{content:""}.c4l-allpurposecard-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_allpurposecard_icon]])}.c4l-inlinetag-icon::before{content:""}.c4l-inlinetag-icon .c4l-button-text::before{content:url([[pix:tiny_c4l|c4l_inlinetag_icon]])}.c4lt-dialog-button .c4l-button-variants{display:grid;grid-auto-flow:column;grid-gap:3px;justify-content:end;width:100%;height:42px;margin-right:3px}.c4lt-dialog-button .c4l-button-variants .c4l-button-variant{display:flex;justify-content:center;align-items:center;background-color:#ecf3ff;color:#1679f9;height:42px;width:36px}.c4lt-dialog-button .c4l-button-variants .c4l-button-variant.on,.c4lt-dialog-button .c4l-button-variants .c4l-button-variant:hover{color:#fff;background-color:#1679f9}.c4l-button-variant.align-center-variant-off::before{content:url([[pix:tiny_c4l|variants/align-center-variant-off]])}.c4l-button-variant.align-center-variant-on::before,.c4l-button-variant.align-center-variant-on:hover::before,.c4l-button-variant.align-center-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/align-center-variant-on]])}.c4l-button-variant.align-left-variant-off::before{content:url([[pix:tiny_c4l|variants/align-left-variant-off]])}.c4l-button-variant.align-left-variant-on::before,.c4l-button-variant.align-left-variant-on:hover::before,.c4l-button-variant.align-left-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/align-left-variant-on]])}.c4l-button-variant.align-right-variant-off::before{content:url([[pix:tiny_c4l|variants/align-right-variant-off]])}.c4l-button-variant.align-right-variant-on::before,.c4l-button-variant.align-right-variant-on:hover::before,.c4l-button-variant.align-right-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/align-right-variant-on]])}.c4l-button-variant.caption-variant-off::before{content:url([[pix:tiny_c4l|variants/caption-variant-off]])}.c4l-button-variant.caption-variant-on::before,.c4l-button-variant.caption-variant-on:hover::before,.c4l-button-variant.caption-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/caption-variant-on]])}.c4l-button-variant.comfort-reading-variant-off::before{content:url([[pix:tiny_c4l|variants/comfort-reading-variant-off]])}.c4l-button-variant.comfort-reading-variant-on::before,.c4l-button-variant.comfort-reading-variant-on:hover::before,.c4l-button-variant.comfort-reading-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/comfort-reading-variant-on]])}.c4l-button-variant.dont-card-only-variant-off::before{content:url([[pix:tiny_c4l|variants/dont-card-only-variant-off]])}.c4l-button-variant.dont-card-only-variant-on::before,.c4l-button-variant.dont-card-only-variant-on:hover::before,.c4l-button-variant.dont-card-only-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/dont-card-only-variant-on]])}.c4l-button-variant.full-width-variant-off::before{content:url([[pix:tiny_c4l|variants/full-width-variant-off]])}.c4l-button-variant.full-width-variant-on::before,.c4l-button-variant.full-width-variant-on:hover::before,.c4l-button-variant.full-width-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/full-width-variant-on]])}.c4l-button-variant.ordered-list-variant-off::before{content:url([[pix:tiny_c4l|variants/ordered-list-variant-off]])}.c4l-button-variant.ordered-list-variant-on::before,.c4l-button-variant.ordered-list-variant-on:hover::before,.c4l-button-variant.ordered-list-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/ordered-list-variant-on]])}.c4l-button-variant.quote-variant-off::before{content:url([[pix:tiny_c4l|variants/quote-variant-off]])}.c4l-button-variant.quote-variant-on::before,.c4l-button-variant.quote-variant-on:hover::before,.c4l-button-variant.quote-variant-off:hover::before{content:url([[pix:tiny_c4l|variants/quote-variant-on]])}.c4l-spacer{display:block;height:12px}.c4l-spacer+.c4l-keyconcept,.c4l-spacer+.c4lv-keyconcept,.c4l-spacer+.c4l-tip,.c4l-spacer+.c4lv-tip,.c4l-spacer+.c4l-reminder,.c4l-spacer+.c4lv-reminder,.c4l-spacer+.c4l-attention,.c4l-spacer+.c4lv-attention,.c4l-spacer+.c4l-quote,.c4l-spacer+.c4lv-quote,.c4l-spacer+.c4l-example,.c4l-spacer+.c4lv-example,.c4l-spacer+.c4l-expectedfeedback,.c4l-spacer+.c4lv-expectedfeedback,.c4l-spacer+.c4l-learningoutcomes,.c4l-spacer+.c4lv-learningoutcomes{margin:0 auto}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-do,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-do{margin-top:0}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-dont,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-dont{margin-bottom:0}.c4l-spacer+.c4l-readingcontext,.c4l-spacer+.c4lv-readingcontext{margin-top:16px;margin-bottom:4px}.c4l-spacer+.c4l-figure,.c4l-spacer+.c4lv-figure{margin:24px auto 14px auto}.c4l-spacer+.c4l-proceduralcontext,.c4l-spacer+.c4lv-proceduralcontext{margin-bottom:0;padding-top:0;padding-bottom:0}.c4l-inline-group{display:flex;flex-direction:row;align-content:flex-end;justify-content:flex-end}.c4l-display-left{display:flex;flex-direction:row;align-content:flex-start;justify-content:flex-start}.c4l-spacer+.c4l-display-left{margin-top:0}.c4l-embedded-caption{font-size:13px;margin-top:12px;text-align:right}.c4l-embedded-caption span{text-transform:uppercase;font-size:14px}.c4l-embedded-caption span::after{content:", "}.c4l-keyconcept,.c4lv-keyconcept{min-width:200px;max-width:99%;background-color:#f1f5fe;padding:24px 36px 30px 36px;border:none;border-left:6px solid #387af1;margin:36px auto;position:relative;border-radius:0}.c4l-keyconcept p:last-of-type,.c4lv-keyconcept p:last-of-type{margin-bottom:0}.c4l-tip,.c4lv-tip{min-width:200px;max-width:99%;background-color:#fbeffa;padding:24px 48px 30px 36px;border:none;border-left:6px solid #b00ca9;margin:36px auto;position:relative;border-radius:0}.c4l-tip p:last-of-type,.c4lv-tip p:last-of-type{margin-bottom:0}.c4l-tip::after,.c4lv-tip::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_tip]]);position:absolute;top:6px;right:-3px}.c4l-reminder,.c4lv-reminder{min-width:200px;max-width:99%;background-color:#eff8fd;padding:24px 48px 30px 36px;border:none;border-left:6px solid #16b9ff;margin:36px auto;position:relative;border-radius:0}.c4l-reminder p:last-of-type,.c4lv-reminder p:last-of-type{margin-bottom:0}.c4l-reminder::after,.c4lv-reminder::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_reminder]]);position:absolute;top:6px;right:-3px}.c4l-attention,.c4lv-attention{min-width:200px;max-width:99%;background-color:#fef6ed;padding:24px 48px 30px 36px;border:none;border-left:6px solid #f88923;margin:36px auto;position:relative;border-radius:0}.c4l-attention p:last-of-type,.c4lv-attention p:last-of-type{margin-bottom:0}.c4l-attention::after,.c4lv-attention::after{content:url([[pix:tiny_c4l|c4l_attention]]);position:absolute;top:6px;right:-3px}.c4l-quote,.c4lv-quote{font-family:Iowan Old Style,Apple Garamond,Baskerville,Times New Roman,Droid Serif,Times,Source Serif Pro,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:16px;line-height:24px;margin:24px auto;min-width:200px;max-width:100%}.c4l-quote .c4l-quote-body,.c4lv-quote .c4l-quote-body{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:flex-start;align-items:stretch;align-content:space-between;font-style:italic}.c4l-quote .c4l-quote-line,.c4lv-quote .c4l-quote-line{border-left:4px solid #387af1;margin-right:16px;margin-top:4px;margin-bottom:2px}.c4l-quote .c4l-quote-text::before,.c4lv-quote .c4l-quote-text::before{content:"";position:static;top:0;margin-right:0}.c4l-quote .c4l-quote-text::after,.c4lv-quote .c4l-quote-text::after{content:"";position:static;top:0;margin-left:0}.c4l-quote .c4l-quote-text p:first-of-type::before,.c4lv-quote .c4l-quote-text p:first-of-type::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_quote_open]]);position:relative;top:-4px;margin-right:2px}.c4l-quote .c4l-quote-text p:last-of-type::after,.c4lv-quote .c4l-quote-text p:last-of-type::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_quote_close]]);position:relative;top:-2px;margin-left:2px}.c4l-quote .c4l-quote-text p:last-of-type,.c4lv-quote .c4l-quote-text p:last-of-type{margin-bottom:0}.c4l-quote .c4l-quote-caption{font-size:13px;margin-top:12px;text-align:right}.c4l-quote .c4l-quote-caption span{text-transform:uppercase;font-size:14px}.c4l-quote .c4l-quote-caption span::after{content:", "}.c4l-dodontcards .c4l-dodontcards-do,.c4l-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-do{margin-top:36px}.c4l-dodontcards .c4l-dodontcards-dont,.c4l-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-dont{margin-bottom:36px}.c4l-dodontcards>.c4l-dodontcards-dont,.c4lv-dodontcards>.c4l-dodontcards-dont{margin-top:12px}.c4l-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-do{min-width:200px;max-width:100%;background:#f1fbf5;border-radius:10px;padding:24px 48px 30px 36px;margin:12px auto;position:relative}.c4l-dodontcards .c4l-dodontcards-do::before,.c4lv-dodontcards .c4l-dodontcards-do::before{content:url([[pix:tiny_c4l|c4l_docard]]);position:absolute;top:12px;right:12px}.c4l-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-dont{min-width:200px;max-width:100%;background:#ffefef;border-radius:10px;padding:24px 48px 30px 36px;margin:12px auto;position:relative}.c4l-dodontcards .c4l-dodontcards-dont::before,.c4lv-dodontcards .c4l-dodontcards-dont::before{content:url([[pix:tiny_c4l|c4l_dontcard]]);position:absolute;top:12px;right:12px}.c4l-dodontcards .c4l-dodontcards-do p,.c4l-dodontcards .c4l-dodontcards-dont p,.c4lv-dodontcards .c4l-dodontcards-do p,.c4lv-dodontcards .c4l-dodontcards-dont p{margin-bottom:6px}.c4l-dodontcards .c4l-dodontcards-do p:last-of-type,.c4l-dodontcards .c4l-dodontcards-dont p:last-of-type,.c4lv-dodontcards .c4l-dodontcards-do p:last-of-type,.c4lv-dodontcards .c4l-dodontcards-dont p:last-of-type{margin-bottom:0}.c4l-readingcontext,.c4lv-readingcontext{min-width:200px;max-width:75%;background-color:#fff;box-shadow:0 4px 24px rgba(0,0,0,.08);box-sizing:border-box;margin:36px auto}.c4l-readingcontext p,.c4lv-readingcontext p{font-size:16px;line-height:23px}.c4l-readingcontext{padding:30px 40px 19px 40px;font-family:Iowan Old Style,Apple Garamond,Baskerville,Times New Roman,Droid Serif,Times,Source Serif Pro,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.c4l-readingcontext .c4l-readingcontext-caption{font-size:16px;margin-top:24px;text-align:right;padding-bottom:14px;font-style:italic}.c4l-readingcontext .c4l-readingcontext-caption span{text-transform:uppercase;font-size:16px;font-style:normal}.c4l-readingcontext .c4l-readingcontext-caption span::after{content:", "}.c4lv-readingcontext{padding:30px 40px 32px 40px;font-family:sans-serif}.c4lv-readingcontext p:last-of-type{margin-bottom:0}.c4lv-readingcontext .c4l-embedded-caption{margin-top:1rem;font-size:16px;font-style:italic}.c4lv-readingcontext.c4l-comfort-reading-variant{font-family:Iowan Old Style,Apple Garamond,Baskerville,Times New Roman,Droid Serif,Times,Source Serif Pro,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.c4l-example,.c4lv-example{font-size:15px;line-height:22px;color:inherit;background:#fff;border-radius:0;margin:36px auto;min-width:75%;max-width:100%;padding:18px 24px;position:relative;box-shadow:0 4px 24px rgba(0,0,0,.13)}.c4l-example h1,.c4lv-example h1{font-weight:700;font-size:11px;line-height:21px;letter-spacing:.5px;color:#3171e3;margin:0 0 24px 0;text-transform:uppercase;font-family:inherit;display:inline-block;border-bottom:2px solid #3171e3}.c4l-figure,.c4lv-figure{min-width:200px;margin:48px auto}.c4l-figure img,.c4lv-figure img{width:100%}.c4l-figure img:not([src]),.c4l-figure img[src=""],.c4lv-figure img:not([src]),.c4lv-figure img[src=""]{content:url([[pix:tiny_c4l|c4l_figure_placeholder]])}.c4l-figure figcaption,.c4lv-figure figcaption{font-size:13px;line-height:16px;color:#686d79;margin-top:7px}.c4l-figure .c4l-figure-footer::after,.c4lv-figure .c4l-figure-footer::after{content:" | ";font-weight:normal;font-style:normal}.c4l-figure .c4l-figure-caption,.c4lv-figure .c4l-figure-caption{font-style:normal;font-size:12px}.c4l-figure figcaption span strong,.c4lv-figure figcaption span strong{font-weight:700}.c4l-figure{max-width:100%}.c4lv-figure{max-width:75%}.c4l-tag,.c4lv-tag{display:inline-block;font-size:10px;font-weight:700;color:#2167cf;background-color:#f1f5fe;border-radius:30px;padding:4px 17px;line-height:20px;margin-bottom:24px;text-transform:uppercase;letter-spacing:.2px}.c4lv-tag.c4l-align-right-variant{margin-left:auto}.c4l-estimatedtime,.c4lv-estimatedtime{font-size:12px;color:#2167cf;background-color:#f1f5fe;padding:6px 14px;font-weight:700;margin-left:6px;margin-bottom:24px;padding-top:5px;padding-bottom:5px;padding-left:36px;position:relative;border-radius:5px}.c4l-estimatedtime span,.c4lv-estimatedtime span{font-weight:600;font-size:10px}.c4l-estimatedtime::before,.c4lv-estimatedtime::before{content:url([[pix:tiny_c4l|noun_project_icons/c4l_estimatedtime]]);position:absolute;left:12px}.c4lv-estimatedtime.c4l-align-left-variant{margin-right:auto;margin-left:0}.c4l-duedate,.c4lv-duedate{font-size:12px;color:#2167cf;background-color:#f1f5fe;padding:6px 14px;font-weight:600;margin-left:6px;margin-bottom:24px;padding-top:5px;padding-bottom:5px;padding-left:36px;position:relative;border-radius:5px}.c4l-duedate::after,.c4lv-duedate::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_duedate]]);position:absolute;left:12px}.c4lv-duedate.c4l-align-left-variant{margin-right:auto;margin-left:0}.c4l-proceduralcontext,.c4lv-proceduralcontext{font-style:italic;color:#3a56af;margin-bottom:12px;padding-top:24px;padding-bottom:24px;font-weight:400}.c4l-gradingvalue,.c4lv-gradingvalue{font-size:12px;color:#2167cf;background-color:#f1f5fe;padding:6px 14px;font-weight:700;margin-left:6px;margin-bottom:24px;padding-top:5px;padding-bottom:5px;padding-left:36px;position:relative;border-radius:5px}.c4l-gradingvalue span,.c4lv-gradingvalue span{font-weight:600;font-size:10px}.c4l-gradingvalue::after,.c4lv-gradingvalue::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_gradingvalue]]);position:absolute;left:12px;top:6px}.c4lv-gradingvalue.c4l-align-left-variant{margin-right:auto;margin-left:0}.c4l-expectedfeedback,.c4lv-expectedfeedback{min-width:200px;max-width:90%;background-color:#fff;padding:24px 36px 30px 36px;margin:36px auto;font-style:italic;position:relative;border-radius:8px;border:none;box-shadow:0 4px 24px rgba(0,0,0,.13)}.c4l-expectedfeedback::before,.c4lv-expectedfeedback::before{content:""}.c4l-expectedfeedback::after,.c4lv-expectedfeedback::after{content:url([[pix:tiny_c4l|noun_project_icons/c4l_expectedfeedback]]);position:absolute;bottom:6px;right:-3px}.c4l-expectedfeedback p:last-of-type,.c4lv-expectedfeedback p:last-of-type{margin-bottom:0}.c4l-allpurposecard,.c4lv-allpurposecard{min-width:200px;max-width:100%;background:#f1f5fe;border-radius:10px;padding:24px 48px 30px 36px;margin:24px auto;position:relative}.c4l-allpurposecard p,.c4lv-allpurposecard p{margin-bottom:6px}.c4l-allpurposecard p:last-of-type,.c4lv-allpurposecard p:last-of-type{margin-bottom:0}.c4l-spacer+.c4l-allpurposecard{margin:0 auto}.c4l-inlinetag,.c4lv-inlinetag{font-weight:900;font-size:10px;text-transform:uppercase;letter-spacing:.2px;display:inline-block;color:#fff;background:#3171e3;border-radius:7px;padding:2px 7px 1px 7px;position:relative;top:-2px;margin-left:4px;margin-right:4px}.c4l-learningoutcomes,.c4lv-learningoutcomes{min-width:200px;max-width:99%;background-color:#f2f5fd;padding:24px 48px 30px 36px;border:none;margin:36px auto;position:relative;border-radius:0}.c4l-learningoutcomes p:last-of-type,.c4lv-learningoutcomes p:last-of-type{margin-bottom:0}.c4l-learningoutcomes .c4l-learningoutcomes-title,.c4lv-learningoutcomes .c4l-learningoutcomes-title{position:relative;top:-11px;left:-39px;padding:6px 14px 5px;border-top-right-radius:3px;border-bottom-right-radius:3px;font-weight:600;font-size:11px;letter-spacing:.7px;color:#fff;background-color:#497ae9;text-transform:uppercase;font-family:inherit;display:inline-block;margin-top:0;filter:drop-shadow(0 1.55601px 3.11202px rgba(0, 0, 0, 0.07))}.c4l-learningoutcomes .c4l-learningoutcomes-title::before,.c4lv-learningoutcomes .c4l-learningoutcomes-title::before{content:url([[pix:tiny_c4l|c4l_learningoutcomes_shadow]]);position:absolute;width:3px;height:2px;top:15.5px;left:.5px}.c4l-learningoutcomes .c4l-learningoutcomes-list,.c4lv-learningoutcomes .c4l-learningoutcomes-list{margin-top:18px;margin-bottom:18px;padding-left:32px}.c4l-learningoutcomes .c4l-learningoutcomes-list>li,.c4lv-learningoutcomes .c4l-learningoutcomes-list>li{position:relative}.c4l-learningoutcomes .c4l-learningoutcomes-list>li:not(:last-child),.c4lv-learningoutcomes .c4l-learningoutcomes-list>li:not(:last-child){margin-bottom:21px}.c4l-learningoutcomes .c4l-learningoutcomes-list li::before,.c4lv-learningoutcomes .c4l-learningoutcomes-list li::before{background-image:url([[pix:tiny_c4l|c4l_learningoutcomes_list_item]]);background-size:9px 11px;display:inline-block;width:9px;height:11px;content:"";justify-self:center;position:absolute;left:-32px;top:7px}.c4l-learningoutcomes .c4l-learningoutcomes-list li::marker,.c4lv-learningoutcomes .c4l-learningoutcomes-list li::marker{color:rgba(0,0,0,0)}.c4lv-learningoutcomes.c4l-ordered-list-variant .c4l-learningoutcomes-list{counter-reset:section}.c4lv-learningoutcomes.c4l-ordered-list-variant .c4l-learningoutcomes-list li::before{background-image:none;position:absolute;left:-32px;top:0;counter-increment:section;content:counter(section) ". ";font-weight:700;color:#497ae9}body.mce-content-body .collapse:not(.show),.c4l-code-preview .collapse:not(.show){display:block !important}@media only screen and (min-width: 576px){.c4l-modal.modal-open .modal-dialog,.c4l-modal-no-preview.modal-open .modal-dialog{min-width:550px}.c4l-select-filters{display:none}.c4l-buttons-filters{display:flex;justify-content:center;align-items:center;margin:16px 0 37px}.c4l-spacer+.c4l-keyconcept,.c4l-spacer+.c4lv-keyconcept,.c4l-spacer+.c4l-tip,.c4l-spacer+.c4lv-tip,.c4l-spacer+.c4l-reminder,.c4l-spacer+.c4lv-reminder,.c4l-spacer+.c4l-attention,.c4l-spacer+.c4lv-attention,.c4l-spacer+.c4l-expectedfeedback,.c4l-spacer+.c4lv-expectedfeedback,.c4l-spacer+.c4l-learningoutcomes,.c4l-spacer+.c4lv-learningoutcomes{margin:24px auto 8px auto}.c4l-spacer+.c4l-quote,.c4l-spacer+.c4lv-quote{margin:12px auto 0 auto}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-do,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-do{margin-top:0}.c4l-spacer+.c4l-dodontcards .c4l-dodontcards-dont,.c4l-spacer+.c4lv-dodontcards .c4l-dodontcards-dont{margin-bottom:0}.c4l-spacer+.c4l-example,.c4l-spacer+.c4lv-example{margin:12px auto 6px auto;padding:36px 48px}.c4l-embedded-caption{margin-top:24px}.c4l-quote,.c4lv-quote{margin:36px auto;max-width:90%}.c4l-quote .c4l-quote-caption{margin-top:24px}.c4l-dodontcards .c4l-dodontcards-do,.c4l-dodontcards .c4l-dodontcards-dont,.c4lv-dodontcards .c4l-dodontcards-do,.c4lv-dodontcards .c4l-dodontcards-dont{max-width:90%;margin:24px auto}.c4l-readingcontext,.c4lv-readingcontext{max-width:88%}.c4l-example,.c4lv-example{max-width:88%;padding:36px 48px}.c4l-expectedfeedback,.c4lv-expectedfeedback{max-width:88%;margin:48px auto}.c4l-allpurposecard,.c4lv-allpurposecard{margin:36px auto;max-width:90%}}@media only screen and (min-width: 768px){.c4l-keyconcept,.c4lv-keyconcept,.c4l-tip,.c4lv-tip,.c4l-reminder,.c4lv-reminder,.c4l-attention,.c4lv-attention,.c4l-dodontcards-do,.c4lv-dodontcards-do,.c4l-dodontcards-dont,.c4lv-dodontcards-dont,.c4l-readingcontext,.c4lv-readingcontext,.c4l-expectedfeedback,.c4lv-expectedfeedback,.c4l-allpurposecard,.c4lv-allpurposecard,.c4l-learningoutcomes,.c4lv-learningoutcomes{max-width:75%}.c4l-keyconcept.c4l-full-width-variant,.c4lv-keyconcept.c4l-full-width-variant,.c4l-tip.c4l-full-width-variant,.c4lv-tip.c4l-full-width-variant,.c4l-reminder.c4l-full-width-variant,.c4lv-reminder.c4l-full-width-variant,.c4l-attention.c4l-full-width-variant,.c4lv-attention.c4l-full-width-variant,.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-do,.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-do,.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4l-readingcontext.c4l-full-width-variant,.c4lv-readingcontext.c4l-full-width-variant,.c4l-expectedfeedback.c4l-full-width-variant,.c4lv-expectedfeedback.c4l-full-width-variant,.c4l-allpurposecard.c4l-full-width-variant,.c4lv-allpurposecard.c4l-full-width-variant,.c4l-learningoutcomes.c4l-full-width-variant,.c4lv-learningoutcomes.c4l-full-width-variant,.c4l-quote.c4l-full-width-variant,.c4lv-quote.c4l-full-width-variant,.c4l-example.c4l-full-width-variant,.c4lv-example.c4l-full-width-variant{max-width:100%}.c4l-quote,.c4lv-quote{margin:48px auto;max-width:75%}.c4l-spacer+.c4l-quote,.c4l-spacer+.c4lv-quote{margin:24px auto 14px auto}.c4l-example,.c4lv-example{margin:48px auto;max-width:75%}.c4l-spacer+.c4l-example,.c4l-spacer+.c4lv-example{margin:24px auto 12px auto}}@media only screen and (min-width: 992px){.c4l-modal-no-preview.modal-open .modal-dialog{max-width:550px}.c4l-buttons-preview{max-height:314px}.c4l-buttons-preview.c4l-no-preview{justify-content:center;max-height:324px}.c4l-buttons-preview.c4l-no-preview .c4l-buttons-grid{grid-gap:9px;justify-content:center;width:405px}.c4l-buttons-preview.c4l-no-preview .c4l-code-preview{display:none}.c4l-buttons-grid{display:grid;grid-template-columns:repeat(auto-fill, 116px);grid-template-rows:repeat(auto-fill, 100px);grid-gap:4px;justify-content:flex-start;width:377px;padding:3px 0 3px 3px}.c4lt-dialog-button.c4l-custom-icon .c4l-button-text i{top:20px;left:0;right:0;margin:0 auto;max-width:25px;min-width:25px;width:100%;border-right:none}.c4lt-dialog-button{height:100px;width:116px;max-width:116px;box-shadow:none;border-radius:4px;margin-bottom:0}.c4lt-dialog-button .c4l-button-text::before{top:20px;left:0;right:0;height:20px;width:26px;margin:0 auto;border-right:none}.c4lt-dialog-button .c4l-button-text{padding:55px 8px 15px 8px;font-size:12px;font-weight:500;line-height:13px;width:80px;text-align:center;justify-content:center}.c4lt-dialog-button .c4l-button-variants{grid-auto-flow:row;grid-gap:0;align-content:flex-start;justify-content:start;width:36px;height:99px;background-color:#ecf3ff;margin:0}.c4lt-dialog-button .c4l-button-variants .c4l-button-variant{display:flex;justify-content:center;align-items:center;color:#1679f9;height:33px;width:36px}.c4lt-dialog-button:hover,.c4lt-dialog-button:active{box-shadow:0 0 0 3px rgba(22,121,249,.3)}.c4lt-dialog-button .c4l-button-variants .c4l-button-variant:first-child,.c4lt-dialog-button .c4l-button-variants .c4l-button-variant:nth-child(2){border-bottom:1px solid #fff}.c4lt-dialog-button:hover .c4l-button-variants .c4l-button-variant{box-shadow:none}.c4l-code-preview{position:relative;display:flex;flex-direction:column;height:300px;width:343px;align-items:center;justify-content:center}.c4l-preview-default{border:1px solid #e1e5ee;border-radius:8px;color:#9297a1;padding:23px 10px;font-weight:400;font-size:12px;line-height:16px;text-align:center;width:60%;margin:0 auto;-webkit-user-select:none;-ms-user-select:none;user-select:none}.c4l-text-preview{position:absolute;top:10px;right:10px;font-weight:600;font-size:9.5px;line-height:11px;letter-spacing:.06em;color:#fff;background-color:#535d76;border-radius:6px;padding:5px;text-transform:uppercase}}.c4l-keyconcept.c4l-full-width-variant,.c4l-quote.c4l-full-width-variant,.c4l-dodontcards.c4l-full-width-variant,.c4l-dodontcards-do,.c4l-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4l-figure.c4l-full-width-variant,.c4l-proceduralcontext.c4l-full-width-variant,.c4l-learningoutcomes.c4l-full-width-variant,.c4l-allpurposecard.c4l-full-width-variant{max-width:100%}.c4l-tip.c4l-full-width-variant,.c4l-reminder.c4l-full-width-variant,.c4l-attention.c4l-full-width-variant{max-width:99%}.c4l-readingcontext.c4l-full-width-variant,.c4l-example.c4l-full-width-variant,.c4l-expectedfeedback.c4l-full-width-variant{max-width:94%}.c4lv-keyconcept.c4l-full-width-variant,.c4lv-quote.c4l-full-width-variant,.c4lv-dodontcards.c4l-full-width-variant,.c4lv-dodontcards-do,.c4lv-dodontcards.c4l-full-width-variant .c4l-dodontcards-dont,.c4lv-figure.c4l-full-width-variant,.c4lv-proceduralcontext.c4l-full-width-variant,.c4lv-learningoutcomes.c4l-full-width-variant,.c4lv-expectedfeedback.c4l-full-width-variant,.c4lv-allpurposecard.c4l-full-width-variant,.c4lv-custom-component.c4l-full-width-variant{max-width:100%}.c4lv-tip.c4l-full-width-variant,.c4lv-reminder.c4l-full-width-variant,.c4lv-attention.c4l-full-width-variant{max-width:99%}.c4lv-readingcontext.c4l-full-width-variant,.c4lv-example.c4l-full-width-variant,.c4lv-expectedfeedback.c4l-full-width-variant{max-width:94%} +#page-lib-editor-tiny-plugins-elements-preview #page{padding-top:0;margin-top:0}#page-lib-editor-tiny-plugins-elements-management .col.item,#page-lib-editor-tiny-plugins-elements-printurls .col.item{min-width:250px;max-width:250px}#page-lib-editor-tiny-plugins-elements-management .col.item.addcontainer .card,#page-lib-editor-tiny-plugins-elements-printurls .col.item.addcontainer .card{background-color:#d3d3d3}#page-lib-editor-tiny-plugins-elements-management .tiny_elements_thumbnail,#page-lib-editor-tiny-plugins-elements-printurls .tiny_elements_thumbnail{width:48px;height:48px}#page-lib-editor-tiny-plugins-elements-management .preview.management,#page-lib-editor-tiny-plugins-elements-printurls .preview.management{display:flex;justify-content:space-evenly;width:100%;height:3rem;padding-bottom:.25rem}#page-lib-editor-tiny-plugins-elements-management .preview.management>div,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div{width:45%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div .elements-button-text,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div .elements-button-text{height:100%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div .elements-button-text::before,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div .elements-button-text::before{display:flex;width:unset;height:100%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more{display:flex;flex-direction:column;justify-content:center;width:10%}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more .elements-button-text::before,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more .elements-button-text::before{all:unset;content:"..."}#page-lib-editor-tiny-plugins-elements-management .preview.management>div.more .elements-button-text,#page-lib-editor-tiny-plugins-elements-printurls .preview.management>div.more .elements-button-text{word-wrap:unset}#page-lib-editor-tiny-plugins-elements-management .compcat:hover .card,#page-lib-editor-tiny-plugins-elements-printurls .compcat:hover .card{border:1px solid #787878}#page-lib-editor-tiny-plugins-elements-management .compcat.active .card,#page-lib-editor-tiny-plugins-elements-printurls .compcat.active .card{border:1px solid #0b5190}#page-lib-editor-tiny-plugins-elements-management .card-body,#page-lib-editor-tiny-plugins-elements-printurls .card-body{display:flex;flex-direction:column;padding-top:.75rem}#page-lib-editor-tiny-plugins-elements-management .card-body>div:hover,#page-lib-editor-tiny-plugins-elements-printurls .card-body>div:hover{cursor:pointer}#page-lib-editor-tiny-plugins-elements-management .card-body>div:hover+a.edit,#page-lib-editor-tiny-plugins-elements-printurls .card-body>div:hover+a.edit{color:#094478}#page-lib-editor-tiny-plugins-elements-management form[data-formtype=tiny_elements_comp_flavor] input.form-control,#page-lib-editor-tiny-plugins-elements-printurls form[data-formtype=tiny_elements_comp_flavor] input.form-control{width:100%}#page-lib-editor-tiny-plugins-elements-management .compcat[data-compcat=found-items] a,#page-lib-editor-tiny-plugins-elements-printurls .compcat[data-compcat=found-items] a{display:none}.elements-plugin-container .nav.nav-tabs{flex-wrap:wrap}.elements-plugin-container .nav.nav-tabs .nav-item a.active{background-color:#f6f7f8;border-bottom:1px solid #f6f7f8}.elements-plugin-container .nav.nav-tabs .nav-item a.active.dropdown-item{color:inherit}.elements-plugin-container .nav.nav-tabs,.elements-plugin-container .nav.nav-tabs .dropdown-item{font-size:.8rem}.elements-plugin-container .nav.nav-tabs .nav-link{border:1px solid #d3d3d3;border-bottom:none;border-top-right-radius:.2rem;border-top-left-radius:.2rem}.elements-buttons-preview{display:flex;justify-content:space-between;width:100%;min-height:31vh;max-height:60vh;background-color:#f6f7f8}.elements-buttons-grid{display:flex;flex-wrap:wrap;justify-content:start;row-gap:12px;column-gap:12px;margin-bottom:0;width:100%;overflow-y:auto}.elements-hidden{display:none !important;max-height:0;padding:0}.elementst-dialog-button{display:flex;flex-direction:row;align-items:center;justify-content:space-between;position:relative;height:48px;max-height:100px;width:100%;max-width:100%;background-color:#fff;border:1px solid #e4e9ec;color:#535d76;box-shadow:0 0 6px 0 rgba(0,0,0,.1);overflow-wrap:anywhere;overflow:hidden;padding:0;margin-bottom:8px}.elementst-dialog-button.elements-custom-icon .elements-button-text i{position:absolute;background-repeat:no-repeat;background-size:contain;background-position:center;min-height:23px;min-width:49px;top:auto;left:0;border-right:1px solid #dce3f0}.elementst-dialog-button .elements-button-text{display:flex;align-items:center;height:100%}.elementst-dialog-button .elements-button-text::before{position:absolute;left:0;height:100%;min-height:23px;display:flex;justify-content:center;align-items:center;border-right:1px solid #dce3f0;width:60px;height:60px;top:3px;align-content:center}.elementst-dialog-button:hover,.elementst-dialog-button:active{box-shadow:inset 0 0 3px rgba(22,121,249,.3)}.elementst-dialog-button .elements-button-text{position:relative;font-size:12px;font-weight:500;width:100%;text-align:left;padding-left:62px}.elements-code-preview{display:none;overflow:hidden;background-color:#fff;border-top:2px solid #f6f7f8}.elements-code-preview .elements-component-code{-webkit-transform:scale(0.6);-ms-transform:scale(0.6);transform:scale(0.6);width:160%;margin-top:35px;position:absolute}.elementst-dialog-button .elements-button-variants{display:grid;grid-auto-flow:column;grid-gap:3px;justify-content:end;width:100%;height:42px;margin-right:3px}.elementst-dialog-button .elements-button-variants .elements-button-variant{display:flex;justify-content:center;align-items:center;background-color:#ecf3ff;color:#1679f9;height:42px;width:36px}.elementst-dialog-button .elements-button-variants .elements-button-variant::before{content:" ";background-size:50%;background-repeat:no-repeat;background-position:center;height:42px;width:36px}.elementst-dialog-button .elements-button-variants .elements-button-variant.on,.elementst-dialog-button .elements-button-variants .elements-button-variant:hover{background-color:#1679f9;color:#fff}.elementst-dialog-button .elements-button-variants .elements-button-variant.on::before,.elementst-dialog-button .elements-button-variants .elements-button-variant:hover::before{filter:brightness(10)}body.mce-content-body .collapse:not(.show),.elements-code-preview .collapse:not(.show){display:block !important}@media only screen and (min-width: 576px){.elements-modal.modal-open .modal-dialog,.elements-modal-no-preview.modal-open .modal-dialog{min-width:550px}.elements-select-filters{display:none}.elements-buttons-filters,.elements-buttons-flavors{display:flex;justify-content:center;align-items:center;margin:10px 0 10px}}@media only screen and (min-width: 992px){.elements-modal-no-preview.modal-open .modal-dialog{max-width:550px}.elements-buttons-preview.elements-no-preview{justify-content:center}.elements-buttons-preview.elements-no-preview .elements-buttons-grid{grid-gap:9px;justify-content:center;width:405px}.elements-buttons-preview.elements-no-preview .elements-code-preview{display:none}.elements-buttons-grid{width:400px;padding:10px}.elementst-dialog-button.elements-custom-icon .elements-button-text i{top:20px;left:0;right:0;margin:0 auto;max-width:25px;min-width:25px;width:100%;border-right:none}.elementst-dialog-button{height:100px;width:116px;max-width:116px;box-shadow:none;border-radius:4px;margin-bottom:0}.elementst-dialog-button::before{content:""}.elementst-dialog-button .elements-button-text::before{left:0;right:0;margin:0 auto;border-right:none;display:inline-block;width:60px;height:60px;top:3px}.elementst-dialog-button .elements-button-text{padding:70px 8px 15px 8px;font-size:12px;font-weight:500;line-height:13px;width:80px;text-align:center;justify-content:center}.elementst-dialog-button .elements-button-variants{grid-auto-flow:row;grid-gap:0;align-content:flex-start;justify-content:start;width:36px;height:99px;background-color:#ecf3ff;margin:0}.elementst-dialog-button .elements-button-variants .elements-button-variant{display:flex;justify-content:center;align-items:center;color:#1679f9;height:33px;width:36px}.elementst-dialog-button:hover,.elementst-dialog-button:active{box-shadow:0 0 0 3px rgba(22,121,249,.3)}.elementst-dialog-button .elements-button-variants .elements-button-variant:first-child,.elementst-dialog-button .elements-button-variants .elements-button-variant:nth-child(2){border-bottom:1px solid #fff}.elementst-dialog-button:hover .elements-button-variants .elements-button-variant{box-shadow:none}.elements-code-preview{position:relative;display:flex;flex-direction:column;width:377px;align-items:center;justify-content:center}.elements-preview-default{border:1px solid #e1e5ee;border-radius:8px;color:#9297a1;padding:23px 10px;font-weight:400;font-size:12px;line-height:16px;text-align:center;width:60%;margin:0 auto;-webkit-user-select:none;-ms-user-select:none;user-select:none}.elements-text-preview{position:absolute;top:10px;right:10px;font-weight:600;font-size:9.5px;line-height:11px;letter-spacing:.06em;color:#fff;background-color:#535d76;border-radius:6px;padding:5px;text-transform:uppercase}} diff --git a/templates/imagepicker.mustache b/templates/imagepicker.mustache new file mode 100644 index 0000000..cccf77a --- /dev/null +++ b/templates/imagepicker.mustache @@ -0,0 +1,47 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_elements/imagepicker + + Template to show image urls in a modal + + Example context (json): + { + "images": [ + { + "id": 1, + "name": "Image 1", + "url": "http://example.com/image1.jpg" + }, + { + "id": 2, + "name": "Image 2", + "url": "http://example.com/image2.jpg" + } + ] + } +}} + +
+
+ {{#images}} +
+
{{name}}
+
+ {{/images}} +
+
diff --git a/templates/imageurls.mustache b/templates/imageurls.mustache new file mode 100644 index 0000000..9879c38 --- /dev/null +++ b/templates/imageurls.mustache @@ -0,0 +1,50 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_elements/imageurls + + Template to show image urls in a modal + + Example context (json): + { + "imageurls": [ + { + "id": 1, + "name": "Image 1", + "url": "http://example.com/image1.jpg" + }, + { + "id": 2, + "name": "Image 2", + "url": "http://example.com/image2.jpg" + } + ] + } +}} + +
+{{#imageurls}} +
+
+
{{url}}
+
+
+
{{name}}
+
+
+{{/imageurls}} +
diff --git a/templates/management.mustache b/templates/management.mustache new file mode 100644 index 0000000..15e61de --- /dev/null +++ b/templates/management.mustache @@ -0,0 +1,254 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_elements/management + + Management page for the tiny_elements plugin. + + Example context (json): + { + "compcats": [ + { + "id": "1", + "name": "just-boxes", + "displayname": "just boxes", + "displayorder": "4" + } + ], + "flavor": [ + { + "id": "1", + "name": "horst-cloud", + "displayname": "Horst und Wölkchen", + "content": "", + "example": "https:\/\/localhost\/pluginfile.php\/1\/tiny_elements\/images\/4\/flavor-horst-cloud\/c4l_Horst_title.svg" + } + ], + "component": [ + { + "id": "1", + "name": "teachpals-tip", + "displayname": "tip", + "compcat": "4", + "code": "" + } + ], + "variant": [ + { + "id": 1, + "name": "Variant1", + "displayname": "Variant 1 Display Name", + "timemodified": 1628572800, + "content": "This is the content for Variant 1.", + "css": "variant1-css-class", + "timecreated": 1628569200 + } + ], + "showbulkedit": true + } +}} + +
+ + +

{{#str}} compcat, tiny_elements {{/str}}

+ + +

{{#str}} flavors, tiny_elements {{/str}}

+ + +
+
+ +
+
+ +

{{#str}} components, tiny_elements {{/str}}

+ + + +
+
+ +
+
+ +

{{#str}} variants, tiny_elements {{/str}}

+ + +
+
+ +
+
+ +
diff --git a/templates/management_import_form_result.mustache b/templates/management_import_form_result.mustache new file mode 100644 index 0000000..bc538dc --- /dev/null +++ b/templates/management_import_form_result.mustache @@ -0,0 +1,36 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_elements/management_import_form_result + + List of results from the import process. + + Example context (json): + { + "results": [ + "Component unchanged", + "New file" + ] + } +}} +
+
    + {{#results}} +
  • {{.}}
  • + {{/results}} +
+
diff --git a/templates/management_preview.mustache b/templates/management_preview.mustache new file mode 100644 index 0000000..d8a05c2 --- /dev/null +++ b/templates/management_preview.mustache @@ -0,0 +1,41 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template tiny_elements/management_preview + + Preview of the component in all flavors. + + Example context (json): + { + "component": "komponente", + "flavors": ["flav", "flav2"], + "config": { + "wwwroot": "http://localhost/moodle" + } + } +}} + +{{< core/modal }} + {{$title}}{{#str}} previewcss, tiny_elements {{/str}}{{/title}} + {{$body}} + {{#str}} previewcsstext, tiny_elements {{/str}} + {{#flavors}} + + {{/flavors}} + {{/body}} +{{/ core/modal }} \ No newline at end of file diff --git a/templates/modal.mustache b/templates/modal.mustache index 89d59e7..7f6d8ab 100644 --- a/templates/modal.mustache +++ b/templates/modal.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template tiny_c4l/modal + @template tiny_elements/modal Modal to manage components within the Tiny Editor. @@ -29,65 +29,135 @@ Example context (json): { - "elementid": "exampleId", - "filters" : [ - {"name": "Contextual"}, - {"type": "contextual"}, - {"filterClass": ""} + "buttons": [ + { + "id": 1, + "name": "tip", + "type": 4, + "imageClass": "elements-teachpals-tip-icon", + "htmlcode": "
", + "variants": [ + { + "id": 8, + "displayname": "Avatar 2", + "name": "avatar2", + "state": "off", + "imageClass": "avatar2-variant-off", + "variantclass": "elements-avatar2-variant", + "content": "" + } ], - "buttons": [ - {"id": "randomid"}, - {"imageClass": "c4l-classname"}, - {"name": "component name"}, - {"htmlcode": "
Text
"} + "flavorlist": "robi-bot,horst-cloud", + "category": 4 + }, + { + "id": 2, + "name": "scenario", + "type": 4, + "imageClass": "elements-teachpals-scenario-icon", + "htmlcode": "
", + "variants": [ + { + "id": 8, + "displayname": "Avatar 2", + "name": "avatar2", + "state": "off", + "imageClass": "avatar2-variant-off", + "variantclass": "elements-avatar2-variant", + "content": "" + } ], - "preview": "true" + "flavorlist": "robi-bot,horst-cloud", + "category": 4 + } + ], + "categories": [ + { + "categoryid": 4, + "name": "Teach Pals", + "type": 4, + "displayorder": 1, + "flavors": [ + { + "id": 1, + "name": "horst-cloud", + "displayname": "Horst und Woelkchen", + "factive": "active" + }, + { + "id": 12, + "name": "robi-bot", + "displayname": "Robi und Bot" + } + ], + "hasFlavors": 2, + "active": "active" + } + ], + "preview": true, + "elementid": "elements-plugin-container", + "canmanage": true } + }} {{< core/modal }} - {{$title}} - {{#str}} pluginname, tiny_c4l {{/str}} - {{/title}} + {{$header}} + + {{#canmanage}} + + {{#pix}} i/settings, core, {{#str}} manage, tiny_elements {{/str}}{{/pix}} + + {{/canmanage}} + {{/header}} {{$body}} -
-
- -
-
- {{#filters}} - - {{/filters}} -
-
-
+
+ +
+
{{#buttons}}
{{#preview}} -
-
{{#str}} preview, tiny_c4l {{/str}}
+
+
{{#str}} preview, tiny_elements {{/str}}
-
{{#str}} previewdefault, tiny_c4l {{/str}}
+
{{#str}} previewdefault, tiny_elements {{/str}}
{{#buttons}} - + {{/buttons}}
{{/preview}} diff --git a/tests/behat/basic.feature b/tests/behat/basic.feature deleted file mode 100644 index 9b149d0..0000000 --- a/tests/behat/basic.feature +++ /dev/null @@ -1,23 +0,0 @@ -@editor @tiny @editor_tiny @tiny_c4l -Feature: Tiny editor components for learning - Write text with the c4l editor plugin - Background: - Given the following "courses" exist: - | shortname | fullname | - | C1 | Course 1 | - And the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Teacher | 1 | teacher1@example.com | - And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | - And the following "activities" exist: - | activity | name | intro | introformat | course | contentformat | idnumber | - | page | PageName1 | PageDesc1 | 1 | C1 | 1 | 1 | - @javascript @external - Scenario: TinyMCE can be used to embed an C4L Content - And I am on the PageName1 "page activity editing" page logged in as admin - And I click on the "C4L" button for the "Page content" TinyMCE editor - And I click on "Tip" "button" - And I press "Save and display" - And I should see "Lorem ipsum" diff --git a/tests/local/utils_test.php b/tests/local/utils_test.php new file mode 100644 index 0000000..902d450 --- /dev/null +++ b/tests/local/utils_test.php @@ -0,0 +1,158 @@ +. + +namespace tiny_elements\local; + +use core\hook\output\before_http_headers; +use stdClass; + +/** + * Test class for the utils functions. + * + * @package tiny_elements + * @copyright 2024 ISB Bayern + * @author Philipp Memmel + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class utils_test extends \advanced_testcase { + /** + * Tests the caching and cache invalidation functionality for the delivering of the tiny_elements css. + * + * @covers \tiny_elements\local\utils::get_complete_css_as_string + * @covers \tiny_elements\local\utils::purge_css_cache + * @covers \tiny_elements\local\utils::rebuild_css_cache + */ + public function test_get_complete_css_as_string(): void { + global $DB; + $this->resetAfterTest(); + + $compcatrecord1 = new stdClass(); + $compcatrecord1->name = 'testcategory1'; + $compcatrecord1->displayname = 'Category 1'; + $compcatrecord1->css = '.testcategory1{margin:3rem}'; + $compcatrecord1id = $DB->insert_record('tiny_elements_compcat', $compcatrecord1); + $compcatrecord2 = new stdClass(); + $compcatrecord2->name = 'testcategory2'; + $compcatrecord2->displayname = 'Category 2'; + $compcatrecord2->css = '.testcategory2{padding:3rem}'; + $compcatrecord2id = $DB->insert_record('tiny_elements_compcat', $compcatrecord2); + + $componentrecord1 = new stdClass(); + $componentrecord1->name = 'testcomponent1'; + $componentrecord1->displayname = 'Component 1'; + + $componentrecord1->compcat = $compcatrecord1id; + $componentrecord1->css = 'div.testcomponent1{background-color:red}'; + $DB->insert_record('tiny_elements_component', $componentrecord1); + + $componentrecord2 = new stdClass(); + $componentrecord2->name = 'testcomponent2'; + $componentrecord2->displayname = 'Component 2'; + + $componentrecord2->compcat = $compcatrecord2id; + $componentrecord2->css = 'p.testcomponent2{background-color:green}'; + $DB->insert_record('tiny_elements_component', $componentrecord2); + + $flavorrecord1 = new stdClass(); + $flavorrecord1->name = 'testflavor1'; + $flavorrecord1->displayname = 'Flavor 1'; + $flavorrecord1->css = '#testflavor{color:blue}'; + $DB->insert_record('tiny_elements_flavor', $flavorrecord1); + + $flavorrecord2 = new stdClass(); + $flavorrecord2->name = 'testflavor2'; + $flavorrecord2->displayname = 'Flavor 2'; + $flavorrecord2->css = '#testflavor2{color:grey}'; + $DB->insert_record('tiny_elements_flavor', $flavorrecord2); + + $flavorrecord3 = new stdClass(); + $flavorrecord3->name = 'testflavor3'; + $flavorrecord3->displayname = 'Flavor 3'; + $flavorrecord3->css = '#testflavor3{color:red}'; + $flavorrecord3->hideforstudents = 1; + $flavorrecord3id = $DB->insert_record('tiny_elements_flavor', $flavorrecord3); + + $starttime = time(); + $this->mock_clock_with_frozen($starttime); + + // We need to initially build the cache. + // This is usually being triggered by the before_http_headers hook. + $mpage = new \moodle_page(); + $rbase = new \renderer_base($mpage, "/"); + $beforehttpheadershook = new before_http_headers($rbase); + hook_callbacks::add_elements_data_to_dom($beforehttpheadershook); + + $css = utils::get_css_from_cache(); + $this->assertStringContainsString($compcatrecord1->css, $css); + $this->assertStringContainsString($compcatrecord2->css, $css); + $this->assertStringContainsString($componentrecord1->css, $css); + $this->assertStringContainsString($componentrecord2->css, $css); + $this->assertStringContainsString($flavorrecord1->css, $css); + $this->assertStringContainsString($flavorrecord2->css, $css); + $this->assertStringContainsString($flavorrecord3->css, $css); + + $dbreadsbefore = $DB->perf_get_queries(); + hook_callbacks::add_elements_data_to_dom($beforehttpheadershook); + $this->assertEquals($dbreadsbefore, $DB->perf_get_queries()); + $this->mock_clock_with_frozen($starttime + 10); + $css = utils::get_css_from_cache(); + $this->assertStringContainsString($compcatrecord1->css, $css); + $this->assertStringContainsString($compcatrecord2->css, $css); + $this->assertStringContainsString($componentrecord1->css, $css); + $this->assertStringContainsString($componentrecord2->css, $css); + $this->assertStringContainsString($flavorrecord1->css, $css); + $this->assertStringContainsString($flavorrecord2->css, $css); + $this->assertStringContainsString($flavorrecord3->css, $css); + + $this->mock_clock_with_frozen($starttime + 20); + $compcatrecord1 = $DB->get_record('tiny_elements_compcat', ['id' => $compcatrecord1id]); + $compcatrecord1->css = 'p{color:pink}'; + $DB->update_record('tiny_elements_compcat', $compcatrecord1); + $flavorrecord3 = $DB->get_record('tiny_elements_flavor', ['id' => $flavorrecord3id]); + $flavorrecord3->hideforstudents = 0; + $DB->update_record('tiny_elements_flavor', $flavorrecord3); + // This needs to be called from the admin interface whenever there is a change in the configuration. + utils::purge_css_cache(); + // Now the callback should trigger a cache rebuild. + $dbreadsbefore = $DB->perf_get_queries(); + hook_callbacks::add_elements_data_to_dom($beforehttpheadershook); + $this->assertGreaterThan($dbreadsbefore, $DB->perf_get_queries()); + $css = utils::get_css_from_cache(); + $this->assertStringContainsString($compcatrecord1->css, $css); + $this->assertStringContainsString($compcatrecord2->css, $css); + $this->assertStringContainsString($componentrecord1->css, $css); + $this->assertStringContainsString($componentrecord2->css, $css); + $this->assertStringContainsString($flavorrecord1->css, $css); + $this->assertStringContainsString($flavorrecord2->css, $css); + $this->assertStringContainsString($flavorrecord3->css, $css); + + // Check if it also works if we purge all the caches of moodle. + purge_all_caches(); + // If we purge the moodle caches the hook callback should trigger a cache rebuild. + $dbreadsbefore = $DB->perf_get_queries(); + hook_callbacks::add_elements_data_to_dom($beforehttpheadershook); + $this->assertGreaterThan($dbreadsbefore, $DB->perf_get_queries()); + $this->mock_clock_with_frozen($starttime + 30); + $css = utils::get_css_from_cache(); + $this->assertStringContainsString($compcatrecord1->css, $css); + $this->assertStringContainsString($compcatrecord2->css, $css); + $this->assertStringContainsString($componentrecord1->css, $css); + $this->assertStringContainsString($componentrecord2->css, $css); + $this->assertStringContainsString($flavorrecord1->css, $css); + $this->assertStringContainsString($flavorrecord2->css, $css); + $this->assertStringContainsString($flavorrecord3->css, $css); + } +} diff --git a/tests/manager_test.php b/tests/manager_test.php new file mode 100644 index 0000000..ba6bd3d --- /dev/null +++ b/tests/manager_test.php @@ -0,0 +1,466 @@ +. + +namespace tiny_elements; + +use advanced_testcase; +use tiny_elements\local\constants; +use tiny_elements\manager; + +/** + * Class manager_test + * + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \tiny_elements\manager + */ +final class manager_test extends advanced_testcase { + /** + * Create a test set. + * + * @param manager $manager + * @return array + */ + public function create_items(manager $manager): array { + $draftitemid = file_get_unused_draft_itemid(); + file_prepare_draft_area($draftitemid, $manager->get_contextid(), 'tiny_elements', 'images', 0, constants::FILE_OPTIONS); + + $categoryid = $manager->add_compcat((object)[ + 'name' => 'test', + 'displayname' => 'test', + 'css' => '', + 'compcatfiles' => $draftitemid, + ]); + $category2id = $manager->add_compcat((object)[ + 'name' => 'test2', + 'displayname' => 'test2', + 'css' => '', + 'compcatfiles' => $draftitemid, + ]); + + $flavorid = $manager->add_flavor((object)[ + 'name' => 'testflavor', + 'displayname' => 'testflavor', + 'css' => '', + 'iconurl' => '', + ]); + $flavor2id = $manager->add_flavor((object)[ + 'name' => 'testflavor2', + 'displayname' => 'testflavor2', + 'css' => '', + 'iconurl' => '', + ]); + + $variantid = $manager->add_variant((object)[ + 'name' => 'testvariant', + 'displayname' => 'testvariant', + 'css' => '', + 'iconurl' => '', + ]); + $variant2id = $manager->add_variant((object)[ + 'name' => 'testvariant2', + 'displayname' => 'testvariant2', + 'css' => '', + 'iconurl' => '', + ]); + + $componentid = $manager->add_component((object)[ + 'name' => 'testcomponent', + 'displayname' => 'testcomponent', + 'css' => '', + 'js' => '', + 'iconurl' => '', + 'flavors' => ['testflavor'], + 'variants' => ['testvariant', 'testvariant2'], + 'categoryname' => 'test', + ]); + $component2id = $manager->add_component((object)[ + 'name' => 'testcomponent2', + 'displayname' => 'testcomponent2', + 'css' => '', + 'js' => '', + 'iconurl' => '', + 'flavors' => ['testflavor', 'testflavor2'], + 'variants' => ['testvariant'], + 'categoryname' => 'test2', + ]); + + return [ + 'categoryid' => $categoryid, + 'category2id' => $category2id, + 'flavorid' => $flavorid, + 'flavor2id' => $flavor2id, + 'componentid' => $componentid, + 'component2id' => $component2id, + 'variantid' => $variantid, + 'variant2id' => $variant2id, + ]; + } + + /** + * Test delete_compcat method. + */ + public function test_delete_compcat(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + // Delete the category. + $manager->delete_compcat($data['categoryid']); + + $this->assertFalse($DB->record_exists('tiny_elements_compcat', ['id' => $data['categoryid']])); + $this->assertFalse($DB->record_exists('tiny_elements_component', ['id' => $data['componentid']])); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent', 'flavorname' => 'testflavor']) + ); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent', 'variant' => 'testvariant']) + ); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent', 'variant' => 'testvariant2']) + ); + + $this->assertTrue($DB->record_exists('tiny_elements_compcat', ['id' => $data['category2id']])); + $this->assertTrue($DB->record_exists('tiny_elements_component', ['id' => $data['component2id']])); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent2', 'flavorname' => 'testflavor']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent2', 'flavorname' => 'testflavor2']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent2', 'variant' => 'testvariant']) + ); + } + + + /** + * Test delete_flavor method. + */ + public function test_delete_flavor(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + // Delete one flavor. + $manager->delete_flavor($data['flavorid']); + + // Verify the flavor is deleted. + $this->assertFalse($DB->record_exists('tiny_elements_flavor', ['id' => $data['flavorid']])); + $this->assertFalse($DB->record_exists('tiny_elements_comp_flavor', ['flavorname' => 'testflavor'])); + + // Verify the other flavor is not deleted. + $this->assertTrue($DB->record_exists('tiny_elements_flavor', ['id' => $data['flavor2id']])); + $this->assertTrue($DB->record_exists('tiny_elements_comp_flavor', ['flavorname' => 'testflavor2'])); + } + + /** + * Test delete_variant method. + */ + public function test_delete_variant(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + // Delete one variant. + $manager->delete_variant($data['variantid']); + + // Verify the variant is deleted. + $this->assertFalse($DB->record_exists('tiny_elements_variant', ['id' => $data['variantid']])); + $this->assertFalse($DB->record_exists('tiny_elements_comp_variant', ['variant' => 'testvariant'])); + + // Verify the other variant is not deleted. + $this->assertTrue($DB->record_exists('tiny_elements_variant', ['id' => $data['variant2id']])); + $this->assertTrue($DB->record_exists('tiny_elements_comp_variant', ['variant' => 'testvariant2'])); + } + + /** + * Test delete_component method. + */ + public function test_delete_component(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + // Delete the component. + $manager->delete_component($data['componentid']); + + // Verify the component is deleted. + $this->assertFalse($DB->record_exists('tiny_elements_component', ['id' => $data['componentid']])); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent', 'flavorname' => 'testflavor']) + ); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent', 'variant' => 'testvariant']) + ); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent', 'variant' => 'testvariant2']) + ); + + // Verify the other component is not deleted. + $this->assertTrue($DB->record_exists('tiny_elements_component', ['id' => $data['component2id']])); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent2', 'flavorname' => 'testflavor']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent2', 'flavorname' => 'testflavor2']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent2', 'variant' => 'testvariant']) + ); + } + + /** + * Test add_compcat method. + * + * @return void + */ + public function test_add_compcat(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $draftitemid = file_get_unused_draft_itemid(); + file_prepare_draft_area($draftitemid, $manager->get_contextid(), 'tiny_elements', 'images', 0, constants::FILE_OPTIONS); + + $categoryid = $manager->add_compcat((object)[ + 'name' => 'test', + 'displayname' => 'test', + 'css' => '', + 'compcatfiles' => $draftitemid, + ]); + + $this->assertTrue($DB->record_exists('tiny_elements_compcat', ['id' => $categoryid])); + } + + /** + * Test add_flavor method. + * + * @return void + */ + public function test_add_flavor(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $flavorid = $manager->add_flavor((object)[ + 'name' => 'testflavor', + 'displayname' => 'testflavor', + 'css' => '', + 'iconurl' => '', + ]); + + $this->assertTrue($DB->record_exists('tiny_elements_flavor', ['id' => $flavorid])); + } + + /** + * Test add_variant method. + * + * @return void + */ + public function test_add_variant(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $variantid = $manager->add_variant((object)[ + 'name' => 'testvariant', + 'displayname' => 'testvariant', + 'css' => '', + 'iconurl' => '', + ]); + + $this->assertTrue($DB->record_exists('tiny_elements_variant', ['id' => $variantid])); + } + + /** + * Test add_component method. + * + * @return void + */ + public function test_add_component(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + $this->assertTrue($DB->record_exists('tiny_elements_component', ['id' => $data['componentid']])); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'testcomponent', 'flavorname' => 'testflavor']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent', 'variant' => 'testvariant']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'testcomponent', 'variant' => 'testvariant2']) + ); + } + + /** + * Test update_compcat method. + */ + public function test_update_compcat(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + $draftitemid = file_get_unused_draft_itemid(); + file_prepare_draft_area($draftitemid, $manager->get_contextid(), 'tiny_elements', 'images', 0, constants::FILE_OPTIONS); + + $manager->update_compcat((object)[ + 'id' => $data['categoryid'], + 'name' => 'changedname', + 'displayname' => 'changeddisplayname', + 'css' => '', + 'compcatfiles' => $draftitemid, + ]); + + $category = $DB->get_record('tiny_elements_compcat', ['id' => $data['categoryid']]); + $this->assertEquals('changedname', $category->name); + $this->assertEquals('changeddisplayname', $category->displayname); + $category2 = $DB->get_record('tiny_elements_compcat', ['id' => $data['category2id']]); + $this->assertEquals('test2', $category2->name); + $this->assertEquals('test2', $category2->displayname); + } + + /** + * Test update_flavor method. + */ + public function test_update_flavor(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + $manager->update_flavor((object)[ + 'id' => $data['flavorid'], + 'name' => 'changedname', + 'displayname' => 'changeddisplayname', + 'css' => '', + ]); + + $flavor = $DB->get_record('tiny_elements_flavor', ['id' => $data['flavorid']]); + $this->assertEquals('changedname', $flavor->name); + $this->assertEquals('changeddisplayname', $flavor->displayname); + $flavor2 = $DB->get_record('tiny_elements_flavor', ['id' => $data['flavor2id']]); + $this->assertEquals('testflavor2', $flavor2->name); + $this->assertEquals('testflavor2', $flavor2->displayname); + } + + /** + * Test update_variant method. + */ + public function test_update_variant(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + $manager->update_variant((object)[ + 'id' => $data['variantid'], + 'name' => 'changedname', + 'displayname' => 'changeddisplayname', + 'css' => '', + 'iconurl' => '', + ]); + + $variant = $DB->get_record('tiny_elements_variant', ['id' => $data['variantid']]); + $this->assertEquals('changedname', $variant->name); + $this->assertEquals('changeddisplayname', $variant->displayname); + $variant2 = $DB->get_record('tiny_elements_variant', ['id' => $data['variant2id']]); + $this->assertEquals('testvariant2', $variant2->name); + $this->assertEquals('testvariant2', $variant2->displayname); + } + + /** + * Test update_component method. + */ + public function test_update_component(): void { + global $DB; + $this->resetAfterTest(true); + $this->setAdminUser(); + $contextid = 1; + $manager = new manager($contextid); + + $data = $this->create_items($manager); + + $manager->update_component((object)[ + 'id' => $data['componentid'], + 'name' => 'changedname', + 'displayname' => 'changeddisplayname', + 'css' => '', + 'js' => '', + 'iconurl' => '', + 'flavors' => ['testflavor2'], + 'variants' => ['testvariant'], + 'categoryname' => 'testcategory2', + ]); + + $component = $DB->get_record('tiny_elements_component', ['id' => $data['componentid']]); + $this->assertEquals('changedname', $component->name); + $this->assertEquals('changeddisplayname', $component->displayname); + $this->assertEquals('testcategory2', $component->categoryname); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'changedname', 'flavorname' => 'testflavor2']) + ); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_flavor', ['componentname' => 'changedname', 'flavorname' => 'testflavor']) + ); + $this->assertTrue( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'changedname', 'variant' => 'testvariant']) + ); + $this->assertFalse( + $DB->record_exists('tiny_elements_comp_variant', ['componentname' => 'changedname', 'variant' => 'testvariant2']) + ); + } +} diff --git a/version.php b/version.php index 9158192..6c8ca22 100644 --- a/version.php +++ b/version.php @@ -15,17 +15,18 @@ // along with Moodle. If not, see . /** - * Tiny C4L plugin version details. + * Tiny Elements plugin version details. * - * @package tiny_c4l - * @copyright 2022 Marc Català + * @package tiny_elements + * @copyright 2025 ISB Bayern + * @author Stefan Hanauska * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); -$plugin->component = 'tiny_c4l'; -$plugin->release = '3.0.1'; -$plugin->requires = 2022112800; -$plugin->maturity = MATURITY_STABLE; -$plugin->version = 2024042901; +$plugin->component = 'tiny_elements'; +$plugin->release = '1.0.0'; +$plugin->requires = 2024042200; +$plugin->maturity = MATURITY_BETA; +$plugin->version = 2025052001;