├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ ├── build-pages.yml │ └── release.yml ├── .gitignore ├── .l10nignore ├── AUTHORS.md ├── CHANGELOG.md ├── COPYING ├── README.md ├── crowdin.yml ├── css └── filepicker.scss ├── examples ├── with-vue.html └── without-vue.html ├── img ├── add.svg ├── audio.svg ├── calendar.svg ├── checked.svg ├── checkmark.svg ├── close.svg ├── disabled-user.svg ├── download.svg ├── file.svg ├── folder.svg ├── history.svg ├── home.svg ├── loading.png ├── location.svg ├── office-document.svg ├── office-presentation.svg ├── password.svg ├── pdf.svg ├── picture.svg ├── public.svg ├── rename.svg ├── spreadsheet.svg ├── text.svg ├── unchecked.svg ├── upload.svg └── video.svg ├── l10n ├── gen_str_po.sh ├── read.sh └── update.sh ├── package-lock.json ├── package.json ├── public ├── oidc-callback.html └── oidc-client.min.js ├── release.sh ├── src ├── FilePicker.js ├── NcWebdavFilePicker.js ├── components │ ├── FileBrowser.vue │ ├── FilePicker.vue │ ├── MyDatetimePicker.vue │ ├── MyVTh.vue │ ├── NcWebdavFilePicker.vue │ ├── NextcloudFileIcon.vue │ └── PickerBreadcrumbs.vue ├── exampleVue.js ├── filePickerWrapper.js ├── services │ └── auth.js ├── translation.js ├── translations.js ├── utils.js ├── views │ └── ExampleApp.vue └── webdavFetchClient.js ├── stylelint.config.js ├── translationfiles └── templates │ └── nextcloud-webdav-filepicker.pot └── webpack.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | appVersion: true 4 | }, 5 | parserOptions: { 6 | requireConfigFile: false 7 | }, 8 | extends: [ 9 | '@nextcloud' 10 | ], 11 | rules: { 12 | 'jsdoc/require-jsdoc': 'off', 13 | 'jsdoc/tag-lines': 'off', 14 | 'vue/first-attribute-linebreak': 'off', 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/build-pages.yml: -------------------------------------------------------------------------------- 1 | name: build-pages 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '14' 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | - name: Set up npm 21 | run: npm i -g npm 22 | 23 | - name: Compile 24 | if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} 25 | run: | 26 | export DEBIAN_FRONTEND=noninteractive 27 | sudo rm -f /etc/apt/sources.list.d/github_git-lfs.list 28 | sudo apt update -y 29 | sudo apt install sed -y 30 | npm ci 31 | npm run build 32 | DATE=`date -u` 33 | sed -i "s/Dev version/Generated at $DATE/" examples/without-vue.html 34 | sed -i "s/Dev version/Generated at $DATE/" examples/with-vue.html 35 | mkdir /tmp/pages 36 | cp -r js examples /tmp/pages/ 37 | rm -rf js node_modules 38 | 39 | - name: Deploy 40 | if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} 41 | uses: peaceiris/actions-gh-pages@v3 42 | with: 43 | github_token: ${{ secrets.GITHUB_TOKEN }} 44 | publish_dir: /tmp/pages 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | on: 4 | push: 5 | branches: [ release ] 6 | pull_request: 7 | branches: [ release ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | environment: release 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 16 19 | registry-url: 'https://registry.npmjs.org' 20 | 21 | - name: Set up npm 22 | run: npm i -g npm@^8.0.0 23 | 24 | - name: Get current tag 25 | id: tag 26 | run: | 27 | git fetch --tags --force 28 | tag=$(git tag -l --points-at HEAD) 29 | vtag=$(echo $tag | grep "^v[0-9]\+\.[0-9]\+\.[0-9]\+" || echo "") 30 | echo "##[set-output name=currenttag;]$vtag" 31 | 32 | - name: Publish 33 | if: ${{ startsWith( steps.tag.outputs.currenttag , 'v' ) }} 34 | run: | 35 | npm ci 36 | npm run build 37 | npm publish 38 | env: 39 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | js/ 2 | .code-workspace 3 | .DS_Store 4 | .idea/ 5 | .vscode/ 6 | .vscode-upload.json 7 | .*.sw* 8 | node_modules 9 | translationfiles/*_* 10 | -------------------------------------------------------------------------------- /.l10nignore: -------------------------------------------------------------------------------- 1 | # compiled vue templates 2 | js/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | * Julien Veyssier (Developper) 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [Unreleased] 8 | 9 | ## 1.0.4 – 2023-03-21 10 | ### Changed 11 | - get quota directly with PROPFIND on root dir, no extra request except after uploading if picker is not closed 12 | - optional button to refresh quota 13 | 14 | ## 1.0.3 – 2023-03-07 15 | ### Fixed 16 | - computed quota value 17 | 18 | ## 1.0.2 – 2023-02-07 19 | ### Added 20 | - new option to avoid closing the picker on error 21 | 22 | ## 1.0.0 – 2022-12-12 23 | ### Added 24 | - new prop/param to disable previews 25 | 26 | ### Changed 27 | - update all npm pkgs 28 | - adjust to new eslint config 29 | - adjust to @nextcloud/vue 7.2.0 (mostly style issues with breadcrumb, popover) 30 | 31 | ## 0.0.26 – 2022-09-07 32 | ### Fixed 33 | - Don't stop everything if there's an error when getting the quota 34 | 35 | ## 0.0.11 – 2021-02-04 36 | ### Added 37 | - public link creation 38 | 39 | ## 0.0.1 – 2020-09-02 40 | ### Added 41 | * the filepicker 42 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📂 Nextcloud WebDav file picker 2 | 3 | ![pages](https://github.com/julien-nc/nextcloud-webdav-filepicker/workflows/build-pages/badge.svg?branch=master&event=push) 4 | 5 | The Nextcloud WebDav file picker is a web component to access Nextcloud files. 6 | 7 | It requires the [WebAppPassword](https://apps.nextcloud.com/apps/webapppassword) to be installed on the target Nextcloud server. 8 | 9 | # Table of Contents 10 | * [📂 Nextcloud WebDav file picker](#s1) 11 | * [ 👀 Demo](#s2) 12 | * [ ▶ Introduction](#s3) 13 | * [Features](#s3-1) 14 | * [Authentication](#s3-2) 15 | * [How to include it](#s3-3) 16 | * [ ✨ The wrapper](#s4) 17 | * [Example](#s4-1) 18 | * [Methods](#s4-2) 19 | * [Events](#s4-3) 20 | * [ 🇻 The Vue component](#s5) 21 | * [Example](#s5-1) 22 | * [Props](#s5-2) 23 | * [Methods](#s5-3) 24 | * [Slots](#s5-4) 25 | * [Events](#s5-5) 26 | * [ 🔧 More information](#s6) 27 | * [WebAppPassword app and CORS headers](#s6-1) 28 | * [Restrictions with OAuth access tokens](#s6-2) 29 | * [Create Nextcloud share links](#s6-3) 30 | * [Save downloaded files](#s6-4) 31 | * [Demo pages GET parameters](#s6-5) 32 | 33 | # 👀 Demo 34 | 35 | * [Example of Vue application using the component](https://julien-nc.github.io/nextcloud-webdav-filepicker/examples/with-vue.html) 36 | * [Example of simple script using the file picker wrapper](https://julien-nc.github.io/nextcloud-webdav-filepicker/examples/without-vue.html) 37 | 38 | See how to [pass GET parameters](#demo-pages-get-parameters) 39 | 40 | # ▶ Introduction 41 | 42 | ## Features 43 | 44 | This file picker is able to 45 | 46 | * Select multiple files and: 47 | * Get their path 48 | * Download them as [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 49 | * Generate WebDav download links 50 | * Help you to easily generate Nextcloud share links 51 | * Select a target directory and: 52 | * Get its path 53 | * Generate a WebDav upload link 54 | * Upload local files 55 | 56 | ## Authentication 57 | 58 | 🔒 Supported authentication methods are: 59 | 60 | * Provide a login and: 61 | * a classic password 62 | * an app password 63 | * an OAuth bearer token (see [OAuth token](#restrictions-with-oauth-access-tokens)) 64 | * Let the file picker authenticate on its own with the web login flow 65 | 66 | ⚠ A login is still required if you want to use an OAuth token. 67 | 68 | ## How to include it 69 | 70 | There are two ways to include this file picker in your web application: 71 | 72 | * A Vue.js component 73 | * A wrapper script 74 | 75 | The file picker can optionally show buttons to open it an perform actions. You can also directly call the component methods to trigger the file picker actions. 76 | 77 | # ✨ The wrapper 78 | 79 | Get it from NPM: 80 | ``` 81 | npm install --save nextcloud-webdav-filepicker 82 | ``` 83 | 84 | And import it: 85 | ``` 86 | 87 | ``` 88 | or 89 | ``` javascript 90 | import('[...]/node_modules/nextcloud-webdav-filepicker/js/filePickerWrapper.js').then(() => { main() }) 91 | ``` 92 | 93 | [Complete single file example](https://github.com/julien-nc/nextcloud-webdav-filepicker/blob/master/examples/without-vue.html) 94 | 95 | Once you've imported `filePickerWrapper.js` you can call the `window.createFilePicker()` function 96 | to mount the file picker somewhere in your web page. This function returns the component to let you interact with it later. 97 | 98 | Parameters of `createFilePicker(mountPoint, options)` function: 99 | 100 | * mountPoint (String): the ID of the element in which the file picker is mounted 101 | * options: initial option values 102 | 103 | Accepted options: 104 | 105 | | key | value | type | default | 106 | | -- | -- | -- | -- | 107 | | url | the Nextcloud base URL | string | mandatory | 108 | | login | the user name | string | - | 109 | | password | the user password, an app password or an OAuth access token | string | - | 110 | | accessToken | an OAuth token (use this parameter if you absolutely want to use HTTP Authorization header to authenticate. Using the OAuth token as a password is recommended, see [OAuth token](#restrictions-with-oauth-access-tokens)) | string | - | 111 | | useCookies | Include cookies in WebDav and OCS requests if true | boolean | false | 112 | | themeColor | the main file picker color | hex color string | Nextcloud blue: `#0082c9` | 113 | | darkMode | toggle the dark theme | boolean | `false` | 114 | | displayPreviews | toggle the file preview display | boolean | `true` | 115 | | multipleDownload | let the user select multiple files in the file picker | boolean | `true` | 116 | | multipleUpload | let the user select multiple local files to upload | boolean | `true` | 117 | | closeOnError | close the picker on network error for getFilesLink, uploadFiles and downloadFiles (like when password policy refuses a link password) | boolean | `false` | 118 | | enableGetFilesPath | show the "Get files path" button | boolean | `false` | 119 | | enableGetFilesLink | show the "Get files link" button | boolean | `false` | 120 | | enableDownloadFiles | show the "Get files link" button | boolean | `false` | 121 | | enableGetSaveFilePath | show the "Get files link" button | boolean | `false` | 122 | | enableGetUploadFileLink | show the "Get files link" button | boolean | `false` | 123 | | enableUploadFiles | show the "Upload files" button | boolean | `false` | 124 | | language | optional language for translation (xx-XX and xx locales accepted) | string | browser locale | 125 | | useWebapppassword | use the webapppassword login flow as fallback when no other method is used or configured | boolean | `true` | 126 | 127 | If login and password/accessToken are not defined, the file picker will let the user authenticate through the web login flow and get an app password by itself. 128 | 129 | ## Example 130 | 131 | Here is a minimal example getting files paths and displaying them in the console: 132 | 133 | ``` html 134 | 135 |
136 | 137 | 138 | 154 | ``` 155 | 156 | ##
Methods 157 | 158 | You can open the file picker by calling opening methods: 159 | 160 | * getFilesPath: open it to get files paths 161 | * getFilesLink: open it to get WebDav links, optional parameter: an object with default link option values 162 | * `linkLabel` (String) to name the created links 163 | * `expirationDate` (Date) 164 | * `protectionPassword` (String) 165 | * `allowEdition` (bool) 166 | * downloadFiles: open it to get files content 167 | * uploadFiles: open the browser file dialog to select local files and then open the filepicker to choose a target directory to upload them 168 | * getSaveFilePath: open it to get a target directory path 169 | * getUploadFileLink: open it to select a target directory and get a WebDav upload link 170 | 171 | Those are the methods of the filepicker component returned by the `window.createFilePicker()` function. 172 | ``` javascript 173 | const filepicker = window.createFilePicker('mount_point', 'https://my.nextcloud.org') 174 | filepicker.getFilesPath() 175 | filepicker.getFilesLink({ 176 | expirationDate: new Date('2050-01-01'), 177 | protectionPassword: 'example passwd', 178 | allowEdition: true, 179 | linkLabel: 'e-mail attachment to Jane', 180 | }) 181 | ``` 182 | 183 | ## Events 184 | 185 | Here are the events emitted by the component and the data they provide in the `detail` attribute: 186 | 187 | * `filepicker-closed`: when the file picker is closed, whatever the reason (no associated data) 188 | * `filepicker-manually-closed`: when the user closes the file picker with the top right close icon (no associated data) 189 | * `filepicker-unauthorized`: when a WebDav request faced a 401 response code 190 | * `response`: the response object 191 | * `get-files-path`: files were selected 192 | * `selection`: an array of file paths 193 | * `files-downloaded`: files were downloaded 194 | * `successFiles` array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 195 | * `errorFilePaths` array of path 196 | * `get-files-link`: links were generated 197 | * `shareLinks` an array of Nextcloud share links, (only if link creation was successful, see [CORS issue](#create-nextcloud-share-links)), `null` if failed 198 | * `linkOptions` user selected option values for link creation (in case you want to make the OCS requests yourself) 199 | * `pathList` list of selected paths 200 | * `ocsUrl` OCS API URL to [create Nextcloud share links](#create-nextcloud-share-links) 201 | * `genericShareLink` an example of share link with "TOKEN" as the token value 202 | * `get-save-file-path`: a target directory was selected 203 | * `path` the path of the selected target directory 204 | * `upload-path-link-generated`: WebDav upload link was generated 205 | * `link` a WebDav upload link 206 | * `targetDir` the target directory path 207 | * `files-uploaded`: 208 | * `successFiles` array of successfully uploaded [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 209 | * `errorFiles` array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 210 | * `targetDir` the target directory path 211 | 212 | You can listen to filepicker events just like other events: 213 | ``` javascript 214 | document.addEventListener('filepicker-manually-closed', (e) => { 215 | console.debug('The file picker was manually closed') 216 | }) 217 | ``` 218 | 219 | # 🇻 The Vue component 220 | 221 | Get it from NPM: 222 | ``` 223 | npm install --save nextcloud-webdav-filepicker 224 | ``` 225 | 226 | And use it: 227 | ``` 228 | import NcWebdavFilePicker from 'nextcloud-webdav-filepicker' 229 | ``` 230 | 231 | [Complete Vue app example](https://github.com/julien-nc/nextcloud-webdav-filepicker/blob/master/src/views/ExampleApp.vue) (top component using the file picker) 232 | 233 | ## Example 234 | 235 | Here is a minimal example of a Vue.js app using the file picker: 236 | ``` vue 237 | 246 | 247 | 274 | ``` 275 | 276 | ## Props 277 | 278 | ``` javascript 279 | /* === reactive props === */ 280 | // Nextcloud base URL 281 | ncUrl: { 282 | type: String, 283 | required: true, 284 | }, 285 | // Nextcloud user name 286 | ncLogin: { 287 | type: String, 288 | default: '', 289 | }, 290 | // Nextcloud user password/app password/OAuth access token 291 | ncPassword: { 292 | type: String, 293 | default: '', 294 | }, 295 | // OAuth access token if you absolutely want to use Bearer Authorization header (if not, using a token as a password works fine) 296 | ncAccessToken: { 297 | type: String, 298 | default: '', 299 | }, 300 | // Include cookies in WebDav and OCS requests if this is true 301 | useCookies: { 302 | type: Boolean, 303 | default: false, 304 | }, 305 | // use WebAppPassword login flow 306 | useWebapppassword: { 307 | type: Boolean, 308 | default: true, 309 | }, 310 | /* === props to control the fp component from the parent one === */ 311 | // file picker mode to determine what is done when the picker is opened 312 | pickerMode: { 313 | type: String, 314 | default: '', 315 | }, 316 | // prop to open the file picker if you don't want to use the buttons 317 | pickerIsOpen: { 318 | type: Boolean, 319 | default: false, 320 | }, 321 | /* === options === */ 322 | // enable multiple selection in all download modes 323 | multipleDownload: { 324 | type: Boolean, 325 | default: true, 326 | }, 327 | // enable multiple local files selection when uploading 328 | multipleUpload: { 329 | type: Boolean, 330 | default: true, 331 | }, 332 | // close the picker on network error for getFilesLink, uploadFiles and downloadFiles (like when password policy refuses a link password) 333 | closeOnError: { 334 | type: Boolean, 335 | default: false, 336 | }, 337 | // file picker title 338 | getTitle: { 339 | type: String, 340 | default: null, 341 | }, 342 | putTitle: { 343 | type: String, 344 | default: null, 345 | }, 346 | // theming (reactive too) 347 | themeColor: { 348 | type: String, 349 | default: '#0082c9', 350 | validator: (value) => { 351 | return value.match(/^#[0-9a-fA-F]{6}$/) 352 | }, 353 | }, 354 | darkMode: { 355 | type: Boolean, 356 | default: false, 357 | }, 358 | displayPreviews: { 359 | type: Boolean, 360 | default: true, 361 | }, 362 | /* === toggle buttons === */ 363 | // display the button to get files path 364 | enableGetFilesPath: { 365 | type: Boolean, 366 | default: false, 367 | }, 368 | // display the button to get files links 369 | enableGetFilesLink: { 370 | type: Boolean, 371 | default: false, 372 | }, 373 | // display the button to download files 374 | enableDownloadFiles: { 375 | type: Boolean, 376 | default: false, 377 | }, 378 | // display the button to get a save file path 379 | enableGetSaveFilePath: { 380 | type: Boolean, 381 | default: false, 382 | }, 383 | // display the button to get webdav upload link 384 | enableGetUploadFileLink: { 385 | type: Boolean, 386 | default: false, 387 | }, 388 | // display the button to upload local files 389 | enableUploadFiles: { 390 | type: Boolean, 391 | default: false, 392 | }, 393 | // optional language for translation (xx-XX and xx locales accepted), use browser locale if null 394 | language: { 395 | type: String, 396 | default: null, 397 | }, 398 | ``` 399 | 400 | ## Methods 401 | 402 | You can also open the file picker by calling opening methods: 403 | 404 | * getFilesPath: open it to get files paths 405 | * getFilesLink: open it to get WebDav links, optional parameter: an object with default link option values 406 | * `linkLabel` (String) to name the created links 407 | * `expirationDate` (Date) 408 | * `protectionPassword` (String) 409 | * `allowEdition` (bool) 410 | * downloadFiles: open it to get files content 411 | * uploadFiles: open the browser file dialog to select local files and then open the filepicker to choose a target directory to upload them 412 | * getSaveFilePath: open it to get a target directory path 413 | * getUploadFileLink: open it to select a target directory and get a WebDav upload link 414 | 415 | To access these methods, put a `ref` to the `NcWebdavFilePicker` component and then: 416 | ``` javascript 417 | this.$refs.myref.getFilesPath() 418 | this.$refs.myref.getFilesLink({ 419 | expirationDate: new Date('2050-01-01'), 420 | protectionPassword: 'example passwd', 421 | allowEdition: true, 422 | linkLabel: 'e-mail attachment to Jane', 423 | }) 424 | ``` 425 | 426 | ## Slots 427 | 428 | There is a slot for each button that triggers a file picker action: 429 | 430 | * get-files-path 431 | * get-files-link 432 | * download-files 433 | * get-save-file-path 434 | * get-upload-fileLink 435 | * open-file-input 436 | 437 | The click event is catched by the file picker component, no need to listen to it. So you can put whatever you want in those slots, a click anywhere in the slot will open the file picker just like if the default button was clicked. 438 | 439 | ## Events 440 | 441 | Those events are emitted by the component and the data included in the associated object: 442 | 443 | * `closed`: when the file picker is closed, whatever the reason (no associated data) 444 | * `manually-closed`: when the user closes the file picker with the top right close icon (no associated data) 445 | * `filepicker-unauthorized`: when a WebDav request faced a 401 response code 446 | * `response`: the response object 447 | * `get-files-path`: files were selected 448 | * `selection`: an array of file paths 449 | * `files-downloaded`: files were downloaded 450 | * `successFiles` array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 451 | * `errorFilePaths` array of path 452 | * `get-files-link`: links were generated 453 | * `shareLinks` an array of Nextcloud share links (only if link creation was successful, see [CORS issue](#create-nextcloud-share-links)), `null` if failed 454 | * `linkOptions` user selected option values for link creation (in case you want to make the OCS requests yourself) 455 | * `pathList` list of selected paths 456 | * `ocsUrl` OCS API URL to [create Nextcloud share links](#create-nextcloud-share-links) 457 | * `genericShareLink` an example of share link with "TOKEN" as the token value 458 | * `get-save-file-path`: a target directory was selected 459 | * `path` the path of the selected target directory 460 | * `upload-path-link-generated`: WebDav upload link was generated 461 | * `link` a WebDav upload link 462 | * `targetDir` the target directory path 463 | * `files-uploaded`: 464 | * `successFiles` array of successfully uploaded [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 465 | * `errorFiles` array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) 466 | * `targetDir` the target directory path 467 | 468 | # 🔧 More information 469 | 470 | ## WebAppPassword app and CORS headers 471 | 472 | Nextcloud includes restrictive CORS headers preventing browsers to perform API requests. 473 | The WebAppPassword app lets Nextcloud admins set a whitelist of allowed origins for WebDav requests. 474 | As the file picker will be included in your web application outside of Nextcloud, 475 | your website's domain needs to be whilelisted in order to make WebDav requests. 476 | 477 | ## Restrictions with OAuth access tokens 478 | 479 | [WebDav client](https://www.npmjs.com/package/webdav) is not able to generate WebDav download/upload links 480 | if the authentication is done via Bearer Authorization (if you pass the OAuth token as the `ncAccessToken` prop). 481 | You can still use an OAuth token to authenticate, just use it like a normal password and pass it as the `ncPassword` prop. 482 | As Nextcloud basic auth supports OAuth tokens, everything will work fine. 483 | 484 | ## Create Nextcloud share links 485 | 486 | As long as the CORS headers can't be changed on Nextcloud side to allow extra origins 487 | (like it's done for WebDav endpoints in WebAppPassword), in most cases, 488 | the browser can't make the OCS requests to create new share links on Nextcloud. 489 | The file picker will still try to make those requests. 490 | They will succeed only if the file picker is used under the same domain as the target Nextcloud. 491 | If it fails, you can still do it anywhere else, on the server side of your web application for example. 492 | The `get-files-link` event provides a share link template and the OCS URL to create such share links. 493 | The OCS API endpoint looks like `https://my.nextcloud.org/ocs/v2.php/apps/files_sharing/api/v1/shares`. 494 | 495 | Here is an example of link creation that you can do on your server side: 496 | 497 | ``` 498 | curl -H "OCS-APIRequest: true" -u login:token -X POST -d "path=/path/to/file&shareType=3" "$OCS_URL" 499 | ``` 500 | 501 | This will create and return a share link (shareType=3) with default permissions. 502 | The share token can be found in `ocs.data.token` of the JSON response. 503 | Then just place the token in the share link template. 504 | For example, if `get-files-link` gave you `https://my.nextcloud.org/index.php/s/TOKEN` as share link template 505 | and the token of the link you created is `wHx2BteGayciKiA`, then the share link is `https://my.nextcloud.org/index.php/s/wHx2BteGayciKiA`. 506 | 507 | Just append `/download` to the share link to trigger the file download instead of displaying the share page. 508 | 509 | ## Save downloaded files 510 | 511 | You can allow users to save the files downloaded by the file picker. 512 | As the returned objects are Files (subclass of Blobs), 513 | you can use [file-saver](https://www.npmjs.com/package/file-saver) to open a save file dialog 514 | and let the browser write the files to local filesystem. 515 | 516 | ## Demo pages GET parameters 517 | 518 | You can pass GET parameters to demo pages in order to initialize field values. It makes it easier to provide demo links directly working. For example: 519 | 520 | https://julien-nc.github.io/nextcloud-webdav-filepicker/examples/with-vue.html?url=https://my.nextcloud.org&login=jason&password=Nm8cC-kHczM-HGz55-S9SE2-Frf4F&color=aa82c9&darkMode=1 (url, login and password are examples) 521 | 522 | Accepted parameters are: 523 | * url 524 | * login 525 | * password 526 | * accessToken 527 | * color (hex color without hash prefix, example: 'aa82c9') 528 | * darkMode ('1' is true, anything else is false) 529 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /translationfiles/templates/nextcloud-webdav-filepicker.pot 3 | translation: /translationfiles/%locale_with_underscore%/nextcloud-webdav-filepicker.po 4 | -------------------------------------------------------------------------------- /css/filepicker.scss: -------------------------------------------------------------------------------- 1 | // as the vue-tooltip element is placed outside our component, we use global style 2 | .v-popper__popper { 3 | font-weight: normal; 4 | font-size: 0.875em; 5 | font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Cantarell,Ubuntu,'Helvetica Neue',Arial,'Noto Color Emoji',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol'; 6 | color: #222; 7 | 8 | &[data-popper-placement="top"] .v-popper__arrow-container { 9 | border-top-color: white !important; 10 | } 11 | &[data-popper-placement="bottom"] .v-popper__arrow-container { 12 | border-bottom-color: white !important; 13 | } 14 | &[data-popper-placement="right"] .v-popper__arrow-container { 15 | border-right-color: white !important; 16 | } 17 | &[data-popper-placement="left"] .v-popper__arrow-container { 18 | border-left-color: white !important; 19 | } 20 | .v-popper__inner { 21 | border-radius: 3px !important; 22 | background: white !important; 23 | filter: drop-shadow(0px 1px 10px rgba(77, 77, 77, 0.5)) !important; 24 | } 25 | } 26 | .v-popper__popper.dark { 27 | color: #d8d8d8; 28 | 29 | &[data-popper-placement="top"] .v-popper__arrow-container { 30 | border-top-color: black !important; 31 | } 32 | &[data-popper-placement="bottom"] .v-popper__arrow-container { 33 | border-bottom-color: black !important; 34 | } 35 | &[data-popper-placement="right"] .v-popper__arrow-container { 36 | border-right-color: black !important; 37 | } 38 | &[data-popper-placement="left"] .v-popper__arrow-container { 39 | border-left-color: black !important; 40 | } 41 | .v-popper__inner { 42 | background: black !important; 43 | filter: drop-shadow(0px 1px 10px rgba(77, 77, 77, 0.5)) !important; 44 | } 45 | } 46 | 47 | // NcButton 48 | .button-vue { 49 | &--vue-secondary { 50 | background-color: var(--color-background-dark) !important; 51 | 52 | &:hover { 53 | background-color: var(--color-background-darker) !important; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/with-vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nextcloud file picker demo 6 | 7 | 8 |
9 |
10 |
11 |

Nextcloud file picker

12 |
13 |
14 |
15 | Dev version 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/without-vue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nextcloud file picker demo 6 | 7 | 8 |
9 |
10 |
11 |

Nextcloud file picker

12 |

13 | This is an example page importing the file picker wrapper. 14 |
15 | No need to use Vue.js to include this component in your web application 😁🎉 16 | Checkout the
source file 17 |

18 |

19 | You can try the file picker with your Nextcloud instance from this page. 20 |
21 | Make sure you installed the 22 | WebAppPassword app 23 | and added as allowed origin in WebAppPassword settings. 24 |

25 |
26 |

27 | When following fields are changed, corresponding component update methods are called. 28 |

29 |

Authentication

30 |

31 | Leave login and password fields empty to let the file picker open an authentication popup and get a temporary app password (web login flow). 32 |

33 | 34 | 35 | 36 | 37 |

Theme color

38 | 39 | 40 |
41 | 42 | 43 |

Custom button which calls the "getFilesPath" method

44 | 45 |

Custom button which calls the "getFilesLink" method

46 | 47 |

48 | File picker component 49 |

50 |

51 | NcWebdavFilePicker component is mounted under the following line. It shows every button by default. 52 |

53 |
54 |
55 |
56 |

Results

57 |

58 | 61 |
62 |
63 | Dev version 64 |
65 | 66 | 67 | 328 | 329 | 358 | -------------------------------------------------------------------------------- /img/add.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/audio.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/calendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/checked.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/disabled-user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/history.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/julien-nc/nextcloud-webdav-filepicker/1a761286352c154f5b83f308e6ddbbcb3758f1d6/img/loading.png -------------------------------------------------------------------------------- /img/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/office-document.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/office-presentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/password.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/pdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/picture.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/public.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/rename.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/spreadsheet.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/unchecked.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/video.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /l10n/gen_str_po.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JS_TR_FILE=../src/translations.js 4 | 5 | echo "export const translations = {" > $JS_TR_FILE 6 | 7 | LOCALES=`ls ../translationfiles | grep -v templates` 8 | for d in $LOCALES ; do 9 | lang=`echo "$d" | sed 's/fr_FR/fr/g' | sed 's/es_ES/es/g' | sed 's/de_DE/de/g' | sed 's/ja_JP/ja/g' | sed 's/ru_RU/ru/g' | sed 's/nl_NL/nl/g' | sed 's/it_IT/it/g' | sed 's/da_DK/da/g' | sed 's/sv_SE/sv/g' | sed 's/tr_TR/tr/g' | sed 's/ko_KR/ko/g' | sed 's/ca_ES/ca/g' | sed 's/ro_RO/ro/g' | sed 's/no_NO/nn/g' | sed 's/cs_CZ/cs/g' | sed 's/fi_FI/fi/g' | sed 's/hu_HU/hu/g' | sed 's/pl_PL/pl/g' | sed 's/sk_SK/sk/g' | sed 's/fa_IR/fa/g' | sed 's/hi_IN/hi/g' | sed 's/id_ID/id/g' | sed 's/uk_UA/uk/g' | sed 's/el_GR/el/g' | sed 's/bg_BG/bg/g' | sed 's/en_GB/en/g' | sed 's/sl_SI/sl/g' | sed 's/af_ZA/af/g' | sed 's/pt_PT/pt/g' | sed "s/pt_BR/'pt-br'/g" | sed 's/ar_SA/ar/g' | sed 's/bn_BD/bn/g' | sed 's/af_ZA/af/g' | sed 's/he_IL/he/g' | sed 's/mn_MN/mn/g' | sed 's/ne_NP/ne/g' | sed 's/sr_SP/sr/g' | sed 's/ta_IN/ta/g' | sed 's/te_IN/te/g' | sed 's/th_TH/th/g' | sed 's/vi_VN/vi/g' | sed "s/zh_CN/'zh-hans'/g" | sed "s/zh_TW/'zh-hant'/g" | sed 's/sq_AL/sq/g' | sed 's|/||g'` 10 | 11 | # skip some languages 12 | if [ "$lang" == "en_US" ] || [ "$lang" == "eo_UY" ] || [ "$lang" == "oc_FR" ] || [ "$lang" == "qu_PE" ] || [ "$lang" == "bo_BT" ]; then 13 | continue 14 | fi 15 | 16 | echo -n " $lang: \`" >> $JS_TR_FILE 17 | cat ../translationfiles/$d/*.po >> $JS_TR_FILE 18 | echo "\`," >> $JS_TR_FILE 19 | done 20 | 21 | echo "}" >> $JS_TR_FILE -------------------------------------------------------------------------------- /l10n/read.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APP_NAME=nextcloud-webdav-filepicker 4 | cd .. 5 | rm -rf /tmp/fpjs 6 | mv js /tmp/fpjs 7 | mv node_modules /tmp/node_modules_fp 8 | translationtool.phar create-pot-files 9 | mv /tmp/fpjs js 10 | mv /tmp/node_modules_fp node_modules 11 | cd - 12 | -------------------------------------------------------------------------------- /l10n/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | APP_NAME=nextcloud-webdav-filepicker 4 | rm -rf /tmp/$APP_NAME 5 | git clone https://github.com/julien-nc/$APP_NAME /tmp/$APP_NAME -b l10n_main --single-branch 6 | cp -r /tmp/$APP_NAME/translationfiles/[a-z][a-z]_[A-Z][A-Z] ../translationfiles/ 7 | rm -rf /tmp/$APP_NAME 8 | 9 | echo "files copied" 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextcloud-webdav-filepicker", 3 | "version": "1.0.6", 4 | "description": "Nextcloud WebDav file picker", 5 | "directories": { 6 | "test": "tests" 7 | }, 8 | "scripts": { 9 | "build": "NODE_ENV=production webpack --progress --config webpack.js", 10 | "dev": "NODE_ENV=development webpack --progress --config webpack.js", 11 | "watch": "NODE_ENV=development webpack --progress --watch --config webpack.js", 12 | "lint": "eslint --ext .js,.vue src", 13 | "lint:fix": "eslint --ext .js,.vue src --fix", 14 | "stylelint": "stylelint src", 15 | "stylelint:fix": "stylelint src --fix", 16 | "serve": "http-server ./ -o examples/with-vue.html -a localhost" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/julien-nc/nextcloud-webdav-filepicker" 21 | }, 22 | "keywords": [ 23 | "filepicker", 24 | "nextcloud", 25 | "webdav" 26 | ], 27 | "author": "Julien Veyssier", 28 | "license": "AGPL-3.0", 29 | "bugs": { 30 | "url": "https://github.com/julien-nc/nextcloud-webdav-filepicker/issues" 31 | }, 32 | "homepage": "https://github.com/julien-nc/nextcloud-webdav-filepicker", 33 | "browserslist": [ 34 | "extends @nextcloud/browserslist-config" 35 | ], 36 | "main": "js/Components/NcWebdavFilePicker.js", 37 | "files": [ 38 | "COPYING", 39 | "README.md", 40 | "src", 41 | "js", 42 | "css", 43 | "examples" 44 | ], 45 | "engines": { 46 | "node": ">=16.0.0", 47 | "npm": "^7.0.0 || ^8.0.0" 48 | }, 49 | "dependencies": { 50 | "@nextcloud/dialogs": "^5.3.1", 51 | "@nextcloud/l10n": "^3.1.0", 52 | "@nextcloud/moment": "^1.1.1", 53 | "@nextcloud/paths": "^2.0.0", 54 | "@nextcloud/router": "^3.0.1", 55 | "@nextcloud/vue": "^8.12.0", 56 | "axios": "^1.2.1", 57 | "base-64": "^1.0.0", 58 | "buffer": "^6.0.3", 59 | "gettext-parser": "^8.0.0", 60 | "oidc-client": "^1.11.5", 61 | "path-browserify": "^1.0.1", 62 | "util": "^0.12.4", 63 | "vue": "^2.6.13", 64 | "vue-click-outside": "^1.1.0", 65 | "vue-material-design-icons": "^5.1.2", 66 | "vue-simple-progress": "^1.1.1", 67 | "vuejs-smart-table": "^0.0.7" 68 | }, 69 | "devDependencies": { 70 | "@nextcloud/babel-config": "^1.0.0", 71 | "@nextcloud/browserslist-config": "^3.0.0", 72 | "@nextcloud/eslint-config": "^8.1.4", 73 | "@nextcloud/stylelint-config": "^2.0.1", 74 | "@nextcloud/webpack-vue-config": "^6.0.0", 75 | "eslint-webpack-plugin": "^4.0.0", 76 | "file-loader": "^6.1.1", 77 | "http-server": "^14.1.0", 78 | "lodash": "^4.17.21", 79 | "process": "^0.11.10", 80 | "stylelint-webpack-plugin": "^5.0.1" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /public/oidc-callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Waiting... 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export NPM_TOKEN=`cat ~/.nextcloud/secrets/NPM_TOKEN | tr -d '\n'` 4 | npm ci 5 | npm run build 6 | NPM_CONFIG_TOKEN=$NPM_TOKEN npm publish 7 | -------------------------------------------------------------------------------- /src/FilePicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2020 Julien Veyssier 3 | * 4 | * @author Julien Veyssier 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import FilePicker from './components/FilePicker.vue' 24 | 25 | export default FilePicker 26 | -------------------------------------------------------------------------------- /src/NcWebdavFilePicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2020 Julien Veyssier 3 | * 4 | * @author Julien Veyssier 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import NcWebdavFilePicker from './components/NcWebdavFilePicker.vue' 24 | 25 | export default NcWebdavFilePicker 26 | -------------------------------------------------------------------------------- /src/components/FileBrowser.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 172 | 173 | 330 | -------------------------------------------------------------------------------- /src/components/FilePicker.vue: -------------------------------------------------------------------------------- 1 | 194 | 195 | 482 | 483 | 667 | -------------------------------------------------------------------------------- /src/components/MyDatetimePicker.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | 34 | 64 | -------------------------------------------------------------------------------- /src/components/MyVTh.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 54 | 55 | 60 | -------------------------------------------------------------------------------- /src/components/NcWebdavFilePicker.vue: -------------------------------------------------------------------------------- 1 | 115 | 116 | 1051 | 1052 | 1165 | -------------------------------------------------------------------------------- /src/components/NextcloudFileIcon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 124 | 125 | 137 | -------------------------------------------------------------------------------- /src/components/PickerBreadcrumbs.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 62 | 63 | 83 | -------------------------------------------------------------------------------- /src/exampleVue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Nextcloud - filepicker 3 | * 4 | * 5 | * This file is licensed under the Affero General Public License version 3 or 6 | * later. See the COPYING file. 7 | * 8 | * @author Julien Veyssier 9 | * @copyright Julien Veyssier 2020 10 | */ 11 | 12 | import Vue from 'vue' 13 | import ExampleApp from './views/ExampleApp.vue' 14 | 15 | const View = Vue.extend(ExampleApp) 16 | new View().$mount('#mount_point') 17 | -------------------------------------------------------------------------------- /src/filePickerWrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Nextcloud - filepicker 3 | * 4 | * 5 | * This file is licensed under the Affero General Public License version 3 or 6 | * later. See the COPYING file. 7 | * 8 | * @author Julien Veyssier 9 | * @copyright Julien Veyssier 2020 10 | */ 11 | 12 | import Vue from 'vue' 13 | import NcWebdavFilePicker from './components/NcWebdavFilePicker.vue' 14 | 15 | window.createFilePicker = (mp, options) => { 16 | const View = Vue.extend(NcWebdavFilePicker) 17 | return new View({ 18 | propsData: { 19 | ncUrl: options.url, 20 | ncLogin: options.login, 21 | ncPassword: options.password, 22 | ncAccessToken: options.accessToken, 23 | ncOidcConfig: options.oidcConfig, 24 | oidcConfigLocation: options.oidcConfigLocation, 25 | useCookies: options.useCookies, 26 | themeColor: options.themeColor, 27 | darkMode: options.darkMode, 28 | displayPreviews: options.displayPreviews, 29 | displayQuotaRefresh: options.displayQuotaRefresh, 30 | multipleDownload: options.multipleDownload, 31 | multipleUpload: options.multipleUpload, 32 | closeOnError: options.closeOnError, 33 | enableGetFilesPath: options.enableGetFilesPath, 34 | enableGetFilesLink: options.enableGetFilesLink, 35 | enableDownloadFiles: options.enableDownloadFiles, 36 | enableGetSaveFilePath: options.enableGetSaveFilePath, 37 | enableGetUploadFileLink: options.enableGetUploadFileLink, 38 | enableUploadFiles: options.enableUploadFiles, 39 | language: options.language, 40 | useWebapppassword: options.useWebapppassword, 41 | }, 42 | }).$mount('#' + mp) 43 | } 44 | -------------------------------------------------------------------------------- /src/services/auth.js: -------------------------------------------------------------------------------- 1 | import { Log, User, UserManager, InMemoryWebStorage, WebStorageStateStore } from 'oidc-client' 2 | 3 | const AUTH_STORAGE_PREFIX = 'nc_oAuth' 4 | 5 | export function initVueAuthenticate(config) { 6 | if (config) { 7 | const storage = config.storage === 'memory' ? new InMemoryWebStorage() : localStorage 8 | const store = new WebStorageStateStore({ 9 | prefix: AUTH_STORAGE_PREFIX, 10 | store: storage, 11 | }) 12 | 13 | const openIdConfig = { 14 | stateStore: store, 15 | userStore: store, 16 | redirect_uri: window.location.origin + '/oidc-callback.html', 17 | response_type: 'code', // code triggers auth code grant flow 18 | response_mode: 'query', 19 | scope: 'openid profile offline_access', 20 | monitorSession: false, 21 | silent_redirect_uri: window.location.origin + '/oidc-silent-redirect.html', 22 | accessTokenExpiringNotificationTime: 10, 23 | automaticSilentRenew: true, 24 | filterProtocolClaims: true, 25 | loadUserInfo: true, 26 | logLevel: Log.INFO, 27 | } 28 | if (config.openIdConnect && config.openIdConnect.authority) { 29 | Object.assign(openIdConfig, config.openIdConnect) 30 | } else { 31 | // old openidconnect setup 32 | if (config.auth.metaDataUrl) { 33 | Object.assign(openIdConfig, { 34 | authority: config.auth.url, 35 | metadataUrl: config.auth.metaDataUrl, 36 | client_id: config.auth.clientId, 37 | redirect_uri: config.auth.redirectUri 38 | ? config.auth.redirectUri 39 | : window.location.origin + '/oidc-callback.html', 40 | }) 41 | } else { 42 | // oauth2 setup 43 | Object.assign(openIdConfig, { 44 | authority: config.auth.url, 45 | // with OAuth2 we need to set the metadata manually 46 | metadata: { 47 | issuer: config.auth.url, 48 | authorization_endpoint: config.auth.authUrl, 49 | token_endpoint: config.auth.url, 50 | userinfo_endpoint: '', 51 | }, 52 | client_id: config.auth.clientId, 53 | redirect_uri: config.auth.redirectUri 54 | ? config.auth.redirectUri 55 | : window.location.origin + '/oidc-callback.html', 56 | response_type: 'token', // token is implicit flow - to be killed 57 | scope: 'openid profile', 58 | loadUserInfo: false, 59 | }) 60 | } 61 | } 62 | 63 | const mgr = new UserManager(openIdConfig) 64 | 65 | Log.logger = console 66 | Log.level = openIdConfig.logLevel 67 | 68 | mgr.events.addUserSignedOut(() => { 69 | console.debug('UserSignedOut:', arguments) 70 | }) 71 | 72 | mgr.events.addSilentRenewError(() => { 73 | mgr.clearStaleState(store, 0) 74 | }) 75 | 76 | return { 77 | authenticate() { 78 | mgr.clearStaleState(store, 0) 79 | return mgr.signinPopup() 80 | }, 81 | getToken() { 82 | const storageString = storage.getItem(AUTH_STORAGE_PREFIX + mgr._userStoreKey) 83 | 84 | if (storageString) { 85 | const user = User.fromStorageString(storageString) 86 | 87 | if (user) { 88 | mgr.events.load(user, false) 89 | if (user.expired) { 90 | mgr.signinSilent().then((_, reject) => { 91 | if (reject) { 92 | mgr.clearStaleState(store, 0) 93 | return null 94 | } 95 | return user.access_token 96 | }) 97 | } else { 98 | return user.access_token 99 | } 100 | } 101 | } 102 | 103 | return null 104 | }, 105 | getStoredUserObject() { 106 | const storageString = storage.getItem(AUTH_STORAGE_PREFIX + mgr._userStoreKey) 107 | 108 | if (storageString) { 109 | const user = User.fromStorageString(storageString) 110 | 111 | if (user) { 112 | mgr.events.load(user, false) 113 | 114 | return user 115 | } 116 | } 117 | 118 | return null 119 | }, 120 | isAuthenticated() { 121 | return this.getToken() !== null 122 | }, 123 | mgr, 124 | events() { 125 | return mgr.events 126 | }, 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/translation.js: -------------------------------------------------------------------------------- 1 | import { translations } from './translations.js' 2 | import { getGettextBuilder } from '@nextcloud/l10n/gettext' 3 | import { po } from 'gettext-parser' 4 | 5 | const langConvertions = { 6 | 'fr-FR': 'fr', 7 | 'es-ES': 'es', 8 | 'de-DE': 'de', 9 | 'ja-JP': 'ja', 10 | 'ru-RU': 'ru', 11 | 'nl-NL': 'nl', 12 | 'it-IT': 'it', 13 | 'da-DK': 'da', 14 | 'sv-SE': 'sv', 15 | 'tr-TR': 'tr', 16 | 'ko-KR': 'ko', 17 | 'ca-ES': 'ca', 18 | 'ro-RO': 'ro', 19 | 'no-NO': 'nn', 20 | 'cs-CZ': 'cs', 21 | 'fi-FI': 'fi', 22 | 'hu-HU': 'hu', 23 | 'pl-PL': 'pl', 24 | 'sk-SK': 'sk', 25 | 'fa-IR': 'fa', 26 | 'hi-IN': 'hi', 27 | 'id-ID': 'id', 28 | 'uk-UA': 'uk', 29 | 'el-GR': 'el', 30 | 'bg-BG': 'bg', 31 | 'en-GB': 'en', 32 | 'sl-SI': 'sl', 33 | 'pt-PT': 'pt', 34 | 'pt-BR': 'pt-br', 35 | 'ar-SA': 'ar', 36 | 'bn-BD': 'bn', 37 | 'af-ZA': 'af', 38 | 'he-IL': 'he', 39 | 'mn-MN': 'mn', 40 | 'ne-NP': 'ne', 41 | 'sr-SP': 'sr', 42 | 'ta-IN': 'ta', 43 | 'te-IN': 'te', 44 | 'th-TH': 'th', 45 | 'vi-VN': 'vi', 46 | 'zh-CN': 'zh-hans', 47 | 'zh-TW': 'zh-hant', 48 | 'sq-AL': 'sq', 49 | } 50 | 51 | let gt 52 | 53 | export function setLanguage(lang = null) { 54 | if (lang === null) { 55 | lang = navigator.language 56 | } 57 | if (lang && lang.length > 2) { 58 | for (const orig in langConvertions) { 59 | lang = lang.replace(orig, langConvertions[orig]) 60 | } 61 | } 62 | 63 | gt = (lang in translations) 64 | ? getGettextBuilder() 65 | // .detectLocale() 66 | .setLanguage(lang) 67 | .addTranslation(lang, po.parse(translations[lang])) 68 | .build() 69 | : getGettextBuilder() 70 | .build() 71 | } 72 | 73 | setLanguage() 74 | 75 | export const t = (appId, string, placeholders = null) => { 76 | return gt.gettext(string, placeholders) 77 | } 78 | export const n = (appId, singular, plural, number, placeholders = null) => { 79 | return gt.ngettext(singular, plural, number, placeholders) 80 | } 81 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | let mytimer = 0 2 | export function delay(callback, ms) { 3 | return function() { 4 | const context = this 5 | const args = arguments 6 | clearTimeout(mytimer) 7 | mytimer = setTimeout(function() { 8 | callback.apply(context, args) 9 | }, ms || 0) 10 | } 11 | } 12 | 13 | export function addCustomEventListener(selector, event, handler) { 14 | const rootElement = document.querySelector('body') 15 | rootElement.addEventListener(event, function(evt) { 16 | let targetElement = evt.target 17 | while (targetElement != null) { 18 | if (targetElement.matches(selector)) { 19 | handler(evt, targetElement) 20 | return 21 | } 22 | targetElement = targetElement.parentElement 23 | } 24 | }, 25 | true) 26 | } 27 | 28 | export function humanFileSize(bytes, approx = false, si = false, dp = 1) { 29 | const thresh = si ? 1000 : 1024 30 | 31 | if (Math.abs(bytes) < thresh) { 32 | return bytes + ' B' 33 | } 34 | 35 | const units = si 36 | ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 37 | : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] 38 | let u = -1 39 | const r = 10 ** dp 40 | 41 | do { 42 | bytes /= thresh 43 | ++u 44 | } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1) 45 | 46 | if (approx) { 47 | return Math.floor(bytes) + ' ' + units[u] 48 | } else { 49 | return bytes.toFixed(dp) + ' ' + units[u] 50 | } 51 | } 52 | 53 | export function colorOpacity(hexColor, opacity) { 54 | // validate hex string 55 | let hex = String(hexColor).replace(/[^0-9a-f]/gi, '') 56 | if (hex.length < 6) { 57 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] 58 | } 59 | 60 | const dec = { 61 | r: parseInt(hex.substr(0, 2), 16), 62 | g: parseInt(hex.substr(2, 2), 16), 63 | b: parseInt(hex.substr(4, 2), 16), 64 | } 65 | return `rgba(${dec.r},${dec.g},${dec.b},${opacity})` 66 | } 67 | 68 | export function colorLuminance(hexColor, lumModifier) { 69 | // validate hex string 70 | let hex = String(hexColor).replace(/[^0-9a-f]/gi, '') 71 | if (hex.length < 6) { 72 | hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] 73 | } 74 | const lum = lumModifier || 0 75 | 76 | // convert to decimal and change luminosity 77 | let rgb = '#' 78 | for (let i = 0; i < 3; i++) { 79 | let c = parseInt(hex.substr(i * 2, 2), 16) 80 | c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16) 81 | rgb += ('00' + c).substr(c.length) 82 | } 83 | 84 | return rgb 85 | } 86 | 87 | export function getElemTypeClass(elem) { 88 | if (elem.type === 'directory') { 89 | return { 'icon-folder': true } 90 | } else { 91 | const mime = elem.mime 92 | if (mime.match(/^video\//)) { 93 | return { 'icon-video': true } 94 | } else if (mime === 'text/calendar') { 95 | return { 'icon-calendar': true } 96 | } else if (mime === 'text/csv' || mime.match(/^application\/.*opendocument\.spreadsheet$/) || mime.match(/^application\/.*office.*sheet$/)) { 97 | return { 'icon-spreadsheet': true } 98 | } else if (mime.match(/^text\//)) { 99 | return { 'icon-text': true } 100 | } else if (mime.match(/^application\/pdf$/)) { 101 | return { 'icon-pdf': true } 102 | } else if (mime.match(/^application\/gpx/)) { 103 | return { 'icon-location': true } 104 | } else if (mime.match(/^image\//)) { 105 | return { 'icon-picture': true } 106 | } else if (mime.match(/^audio\//)) { 107 | return { 'icon-audio': true } 108 | } else if (mime.match(/^application\/.*opendocument\.text$/) || mime.match(/^application\/.*word.*document$/)) { 109 | return { 'icon-office-document': true } 110 | } else if (mime.match(/^application\/.*opendocument\.presentation$/) || mime.match(/^application\/.*office.*presentation$/)) { 111 | return { 'icon-office-presentation': true } 112 | } 113 | return { 'icon-file': true } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/views/ExampleApp.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 287 | 288 | 322 | -------------------------------------------------------------------------------- /src/webdavFetchClient.js: -------------------------------------------------------------------------------- 1 | import base64 from 'base-64' 2 | import { basename } from '@nextcloud/paths' 3 | 4 | const propertyRequestBody = ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ` 22 | 23 | const propertyRequestBodyWithQuota = ` 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ` 43 | 44 | function escapeRegExp(string) { 45 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 46 | } 47 | 48 | export class WebDavFetchClient { 49 | 50 | constructor(options) { 51 | this.xmlParser = new DOMParser() 52 | this.ns = 'DAV:' 53 | 54 | this.url = options.url 55 | const parsedUrl = new URL(this.url) 56 | this.basePath = parsedUrl.pathname 57 | // this is used to compute the relative path (without relying on the base path) 58 | this.userId = options.userId 59 | this.pathRegex = new RegExp('.*' + escapeRegExp('/remote.php/dav/files/' + this.userId), 'g') 60 | 61 | this.username = options.username 62 | this.password = options.password 63 | this.token = options.token 64 | if (this.username && this.password) { 65 | this.authHeader = 'Basic ' + base64.encode(this.username + ':' + this.password) 66 | } else if (this.token) { 67 | this.authHeader = this.token.token_type + ' ' + this.token.access_token 68 | } 69 | this.credentialsMode = options.useCookies 70 | ? 'include' 71 | : 'omit' 72 | } 73 | 74 | appendAuthHeader(headers) { 75 | if (this.authHeader) { 76 | headers.append('Authorization', this.authHeader) 77 | } 78 | } 79 | 80 | getAuthHeader() { 81 | if (this.authHeader) { 82 | return { Authorization: this.authHeader } 83 | } 84 | } 85 | 86 | parseWebDavFileListXML(xmlString, path, getQuota = false) { 87 | const dom = this.xmlParser.parseFromString(xmlString, 'application/xml') 88 | const responseList = dom.documentElement.getElementsByTagNameNS(this.ns, 'response') 89 | const result = { 90 | nodes: [], 91 | } 92 | for (let i = 0; i < responseList.length; i++) { 93 | const node = {} 94 | const e = responseList.item(i) 95 | node.filename = decodeURIComponent(e.getElementsByTagNameNS(this.ns, 'href').item(0).innerHTML) 96 | node.filename = node.filename.replace(this.pathRegex, '').replace(/\/$/, '') 97 | node.fileid = parseInt(e.getElementsByTagName('oc:fileid')?.item(0)?.innerHTML ?? 0) 98 | if (e.getElementsByTagNameNS(this.ns, 'resourcetype').item(0).getElementsByTagNameNS(this.ns, 'collection').length > 0) { 99 | // skip current directory 100 | if (node.filename === path.replace(/\/$/, '')) { 101 | continue 102 | } 103 | node.type = 'directory' 104 | } else { 105 | node.type = 'file' 106 | node.mime = e.getElementsByTagNameNS(this.ns, 'getcontenttype').item(0).innerHTML 107 | node.etag = e.getElementsByTagNameNS(this.ns, 'getetag').item(0).innerHTML 108 | } 109 | node.size = +e.getElementsByTagNameNS(this.ns, 'getcontentlength')?.item(0)?.innerHTML 110 | if (!node.size) { 111 | node.size = +e.getElementsByTagName('oc:size')?.item(0)?.innerHTML 112 | } 113 | node.haspreview = e.getElementsByTagName('nc:has-preview')?.item(0)?.innerHTML === 'true' 114 | node.basename = basename(node.filename) 115 | node.lastmod = e.getElementsByTagNameNS(this.ns, 'getlastmodified').item(0).innerHTML 116 | // elem.Status = e.getElementsByTagNameNS(this.ns, 'status').item(0).innerHTML 117 | result.nodes.push(node) 118 | } 119 | if (getQuota) { 120 | result.quota = this.getQuotaFromParsedXml(dom) 121 | } 122 | return result 123 | } 124 | 125 | getDirectoryContents(path, getQuota = false) { 126 | const headers = new Headers() 127 | headers.append('Accept', 'text/plain') 128 | headers.append('Depth', '1') 129 | headers.append('Content-Type', 'application/xml') 130 | this.appendAuthHeader(headers) 131 | 132 | return new Promise((resolve, reject) => { 133 | fetch(this.url + path, { 134 | method: 'PROPFIND', 135 | credentials: this.credentialsMode, 136 | headers, 137 | body: getQuota ? propertyRequestBodyWithQuota : propertyRequestBody, 138 | }).then((response) => { 139 | response.text().then(text => { 140 | if (response.status < 400) { 141 | resolve(this.parseWebDavFileListXML(text, path, getQuota)) 142 | } else { 143 | reject(new Error({ response }), text) 144 | } 145 | }) 146 | }).catch(err => { 147 | console.error(err) 148 | reject(err) 149 | }) 150 | }) 151 | } 152 | 153 | createDirectory(path) { 154 | const headers = new Headers() 155 | this.appendAuthHeader(headers) 156 | 157 | return new Promise((resolve, reject) => { 158 | fetch(this.url + path, { 159 | method: 'MKCOL', 160 | credentials: this.credentialsMode, 161 | headers, 162 | }).then((response) => { 163 | response.text().then(text => { 164 | if (response.status < 400) { 165 | resolve() 166 | } else { 167 | reject(new Error({ response })) 168 | } 169 | }) 170 | }).catch(err => { 171 | console.error(err) 172 | reject(err) 173 | }) 174 | }) 175 | } 176 | 177 | getFileContents(path) { 178 | const headers = new Headers() 179 | this.appendAuthHeader(headers) 180 | 181 | return fetch(this.url + path, { 182 | method: 'GET', 183 | credentials: this.credentialsMode, 184 | headers, 185 | }) 186 | } 187 | 188 | putFileContents(targetPath, file, options) { 189 | const headers = new Headers() 190 | this.appendAuthHeader(headers) 191 | 192 | return new Promise((resolve, reject) => { 193 | fetch(this.url + targetPath, { 194 | method: 'PUT', 195 | credentials: this.credentialsMode, 196 | headers, 197 | body: file, 198 | }).then((response) => { 199 | if (response.status < 400) { 200 | resolve() 201 | } else { 202 | reject(new Error({ response })) 203 | } 204 | }).catch(err => { 205 | console.error(err) 206 | reject(err) 207 | }) 208 | }) 209 | } 210 | 211 | getQuota() { 212 | const headers = new Headers() 213 | headers.append('Accept', 'text/plain') 214 | headers.append('Depth', '0') 215 | this.appendAuthHeader(headers) 216 | 217 | return new Promise((resolve, reject) => { 218 | fetch(this.url, { 219 | method: 'PROPFIND', 220 | credentials: this.credentialsMode, 221 | headers, 222 | }).then((response) => { 223 | response.text().then(text => { 224 | if (response.status < 400) { 225 | resolve(this.parseWebDavQuotaXML(text)) 226 | } else { 227 | reject(response, text) 228 | } 229 | }) 230 | }).catch(err => { 231 | console.error(err) 232 | reject(err) 233 | }) 234 | }) 235 | } 236 | 237 | parseWebDavQuotaXML(xmlString) { 238 | const dom = this.xmlParser.parseFromString(xmlString, 'application/xml') 239 | return this.getQuotaFromParsedXml(dom) 240 | } 241 | 242 | getQuotaFromParsedXml(dom) { 243 | try { 244 | const used = dom.documentElement.getElementsByTagNameNS(this.ns, 'quota-used-bytes') 245 | const available = dom.documentElement.getElementsByTagNameNS(this.ns, 'quota-available-bytes') 246 | const availableRawValue = available 247 | ? parseInt(available.item(0).innerHTML) 248 | : undefined 249 | // negative -> unlimited 250 | const availableValue = availableRawValue 251 | ? availableRawValue >= 0 252 | ? availableRawValue 253 | : 'unlimited' 254 | : undefined 255 | return { 256 | used: used ? parseInt(used.item(0).innerHTML) : undefined, 257 | available: availableValue, 258 | } 259 | } catch (e) { 260 | console.error('Failed to parse quota', e) 261 | } 262 | return null 263 | } 264 | 265 | getFileDownloadLink(path) { 266 | if (!this.password) { 267 | throw new Error('Impossible to generate download link when login or password is missing.') 268 | } 269 | return this.url.replace(/^(https?:\/\/)([^/]+)\/.*/, '$1' + this.username + ':' + this.password + '@$2') 270 | + path 271 | } 272 | 273 | getFileUploadLink(uploadPath) { 274 | if (!this.password) { 275 | throw new Error('Impossible to generate upload link when login or password is missing.') 276 | } 277 | return this.url.replace(/^(https?:\/\/)/, '$1' + this.username + ':' + this.password + '@') 278 | + uploadPath + '?Content-Type=application/octet-stream' 279 | } 280 | 281 | } 282 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'stylelint-config-recommended-vue', 3 | rules: { 4 | indentation: 'tab', 5 | 'selector-type-no-unknown': null, 6 | 'number-leading-zero': null, 7 | 'rule-empty-line-before': [ 8 | 'always', 9 | { 10 | ignore: ['after-comment', 'inside-block'], 11 | }, 12 | ], 13 | 'declaration-empty-line-before': [ 14 | 'never', 15 | { 16 | ignore: ['after-declaration'], 17 | }, 18 | ], 19 | 'comment-empty-line-before': null, 20 | 'selector-type-case': null, 21 | 'selector-list-comma-newline-after': null, 22 | 'no-descending-specificity': null, 23 | 'string-quotes': 'single', 24 | 'selector-pseudo-element-no-unknown': [ 25 | true, 26 | { 27 | ignorePseudoElements: ['v-deep'], 28 | }, 29 | ], 30 | }, 31 | plugins: ['stylelint-scss'], 32 | } 33 | -------------------------------------------------------------------------------- /translationfiles/templates/nextcloud-webdav-filepicker.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the Nextcloud package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: Nextcloud 3.14159\n" 10 | "Report-Msgid-Bugs-To: translations\\@example.com\n" 11 | "POT-Creation-Date: 2023-09-28 10:09+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 20 | 21 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:1 22 | msgid "Name" 23 | msgstr "" 24 | 25 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:2 26 | msgid "Size" 27 | msgstr "" 28 | 29 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:3 30 | msgid "Modified" 31 | msgstr "" 32 | 33 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:4 34 | msgid "This folder is empty" 35 | msgstr "" 36 | 37 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:5 38 | msgid "Not connected" 39 | msgstr "" 40 | 41 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:6 42 | msgid "Password protect" 43 | msgstr "" 44 | 45 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:7 46 | msgid "Allow editing" 47 | msgstr "" 48 | 49 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:8 50 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:9 51 | msgid "Expires on" 52 | msgstr "" 53 | 54 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:10 55 | msgid "Link settings" 56 | msgstr "" 57 | 58 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:11 59 | msgid "Update quota" 60 | msgstr "" 61 | 62 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:12 63 | msgid "Create new folder" 64 | msgstr "" 65 | 66 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:13 67 | msgid "New folder name" 68 | msgstr "" 69 | 70 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:14 71 | msgid "Cancel" 72 | msgstr "" 73 | 74 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:15 75 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:24 76 | msgid "Ok" 77 | msgstr "" 78 | 79 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:16 80 | msgid "Deselect all" 81 | msgstr "" 82 | 83 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:17 84 | msgid "Select all" 85 | msgstr "" 86 | 87 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:18 88 | msgid "Select one or multiple files" 89 | msgstr "" 90 | 91 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:19 92 | msgid "Select a file" 93 | msgstr "" 94 | 95 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:20 96 | msgid "Save to" 97 | msgstr "" 98 | 99 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:21 100 | #, javascript-format 101 | msgid "{size} used ({percent}% of {total})" 102 | msgstr "" 103 | 104 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:22 105 | msgid "{size} used" 106 | msgstr "" 107 | 108 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:23 109 | msgid "invalid quota" 110 | msgstr "" 111 | 112 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:26 113 | msgid "{nbSelected} selected" 114 | msgid_plural "{nbSelected} selected" 115 | msgstr[0] "" 116 | msgstr[1] "" 117 | 118 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:28 119 | msgid "Get files path" 120 | msgstr "" 121 | 122 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:29 123 | msgid "Get files link" 124 | msgstr "" 125 | 126 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:30 127 | msgid "Download files" 128 | msgstr "" 129 | 130 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:31 131 | msgid "Get save file path" 132 | msgstr "" 133 | 134 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:32 135 | msgid "Get file upload link" 136 | msgstr "" 137 | 138 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:33 139 | msgid "Upload files" 140 | msgstr "" 141 | 142 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:27 143 | msgid "Upload {nbToUpload} file to {path}" 144 | msgid_plural "Upload {nbToUpload} files to {path}" 145 | msgstr[0] "" 146 | msgstr[1] "" 147 | 148 | #: /datadisk/tera/julien/html/nextcloud-webdav-filepicker/specialVueFakeDummyForL10nScript.js:25 149 | msgid "Save to {path}" 150 | msgstr "" 151 | -------------------------------------------------------------------------------- /webpack.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const webpackConfig = require('@nextcloud/webpack-vue-config') 4 | const ESLintPlugin = require('eslint-webpack-plugin') 5 | const StyleLintPlugin = require('stylelint-webpack-plugin') 6 | 7 | const buildMode = process.env.NODE_ENV 8 | const isDev = buildMode === 'development' 9 | webpackConfig.devtool = isDev ? 'cheap-source-map' : 'source-map' 10 | 11 | webpackConfig.stats = { 12 | colors: true, 13 | modules: false, 14 | } 15 | 16 | webpackConfig.entry = { 17 | exampleVue: { import: path.join(__dirname, 'src', 'exampleVue.js'), filename: 'exampleVue.js' }, 18 | wrapper: { import: path.join(__dirname, 'src', 'filePickerWrapper.js'), filename: 'filePickerWrapper.js' }, 19 | mainComponent: { import: path.join(__dirname, 'src', 'NcWebdavFilePicker.js'), filename: 'Components/NcWebdavFilePicker.js' }, 20 | filePickerComponent: { import: path.join(__dirname, 'src', 'FilePicker.js'), filename: 'Components/FilePicker.js' }, 21 | } 22 | 23 | webpackConfig.output.library = 'NcWebdavFilePicker' 24 | webpackConfig.output.libraryTarget = 'umd' 25 | 26 | webpackConfig.resolve.fallback = { 27 | 'path': require.resolve('path-browserify'), 28 | 'buffer': require.resolve('buffer'), 29 | 'util': require.resolve('util'), 30 | } 31 | 32 | webpackConfig.plugins.push( 33 | // fix "process is not defined" error: 34 | // (do "npm install process" before running the build) 35 | new webpack.ProvidePlugin({ 36 | process: 'process/browser', 37 | }), 38 | ) 39 | webpackConfig.plugins.push( 40 | new ESLintPlugin({ 41 | extensions: ['js', 'vue'], 42 | files: 'src', 43 | failOnError: !isDev, 44 | }) 45 | ) 46 | webpackConfig.plugins.push( 47 | new StyleLintPlugin({ 48 | files: 'src/**/*.{css,scss,vue}', 49 | failOnError: !isDev, 50 | }), 51 | ) 52 | 53 | module.exports = webpackConfig 54 | --------------------------------------------------------------------------------