Update site to include help
- Add sphinx documentation generated from build to help - Add link to help (in english) in header - Add link to github in header
4
help/en/.buildinfo
Normal file
@@ -0,0 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: a874776587ce2da814c4810262a52987
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
BIN
help/en/.doctrees/changelog.doctree
Normal file
BIN
help/en/.doctrees/contribute.doctree
Normal file
BIN
help/en/.doctrees/developer/core/app.doctree
Normal file
BIN
help/en/.doctrees/developer/core/directories.doctree
Normal file
BIN
help/en/.doctrees/developer/core/engine.doctree
Normal file
BIN
help/en/.doctrees/developer/core/fs.doctree
Normal file
BIN
help/en/.doctrees/developer/core/gui/deletion_options.doctree
Normal file
BIN
help/en/.doctrees/developer/core/gui/index.doctree
Normal file
BIN
help/en/.doctrees/developer/core/index.doctree
Normal file
BIN
help/en/.doctrees/developer/core/results.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/build.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/conflict.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/desktop.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/base.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/column.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/progress_window.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/selectable_list.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/table.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/text_field.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/gui/tree.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/index.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/jobprogress/job.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/jobprogress/qt.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/notify.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/path.doctree
Normal file
BIN
help/en/.doctrees/developer/hscommon/util.doctree
Normal file
BIN
help/en/.doctrees/developer/index.doctree
Normal file
BIN
help/en/.doctrees/environment.pickle
Normal file
BIN
help/en/.doctrees/faq.doctree
Normal file
BIN
help/en/.doctrees/folders.doctree
Normal file
BIN
help/en/.doctrees/index.doctree
Normal file
BIN
help/en/.doctrees/preferences.doctree
Normal file
BIN
help/en/.doctrees/quick_start.doctree
Normal file
BIN
help/en/.doctrees/reprioritize.doctree
Normal file
BIN
help/en/.doctrees/results.doctree
Normal file
BIN
help/en/.doctrees/scan.doctree
Normal file
705
help/en/_sources/changelog.rst.txt
Normal file
@@ -0,0 +1,705 @@
|
||||
:tocdepth: 1
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
**About the word "crash":** When reading this changelog, you might be alarmed at the number of fixes
|
||||
for "crashes". Be aware that when the word "crash" is used here, it refers to "soft crashes" which
|
||||
don't cause the application to quit. You simply get an error window that asks you if you want to
|
||||
send the crash report to Hardcoded Software. Crashes that cause the application to quit are called
|
||||
"hard crashes" in this changelog.
|
||||
|
||||
|
||||
4.0.3 (2016-11-24)
|
||||
----------------------
|
||||
|
||||
* Add new picture cache backend: shelve
|
||||
* Make shelve picture cache backend the active one on MacOS to fix `#394 <https://github.com/hsoft/dupeguru/issues/394>`__ more
|
||||
elegantly. [cocoa]
|
||||
* Remove Sparkle (auto-updates) due to technical limitations. [cocoa]
|
||||
|
||||
|
||||
4.0.2 (2016-10-09)
|
||||
----------------------
|
||||
|
||||
* Fix systematic crash in Picture Mode under MacOS Sierra. (`#394 <https://github.com/hsoft/dupeguru/issues/394>`__)
|
||||
* No change for Linux. Just keeping version in sync.
|
||||
|
||||
|
||||
4.0.1 (2016-08-24)
|
||||
----------------------
|
||||
|
||||
* Add Greek localization, by Gabriel Koutilellis. (`#382 <https://github.com/hsoft/dupeguru/issues/382>`__)
|
||||
* Fix localization base path. [qt] (`#378 <https://github.com/hsoft/dupeguru/issues/378>`__)
|
||||
* Fix broken load results dialog. [qt]
|
||||
* Fix crash on load results. [cocoa] (`#380 <https://github.com/hsoft/dupeguru/issues/380>`__)
|
||||
* Save preferences more predictably. [qt] (`#379 <https://github.com/hsoft/dupeguru/issues/379>`__)
|
||||
* Fix picture mode's fuzzy block scanner threshold. (`#387 <https://github.com/hsoft/dupeguru/issues/387>`__)
|
||||
|
||||
|
||||
4.0.0 (2016-07-01)
|
||||
----------------------
|
||||
|
||||
* Merge Standard, Music and Picture editions in the same application!
|
||||
* Improve documentation. (`#294 <https://github.com/hsoft/dupeguru/issues/294>`__)
|
||||
* Add Polish, Korean, Spanish and Dutch localizations.
|
||||
* qt: Fix wrong use_regexp option propagation to core. (`#295 <https://github.com/hsoft/dupeguru/issues/295>`__)
|
||||
* qt: Fix progress window mistakenly showing up on startup. (`#357 <https://github.com/hsoft/dupeguru/issues/357>`__)
|
||||
* Bump Python requirement to v3.4.
|
||||
* Bump OS X requirement to 10.8
|
||||
* Drop Windows support, maybe temporarily.
|
||||
`Details <https://www.hardcoded.net/archive2015`#2015 <https://github.com/hsoft/dupeguru/issues/2015>`__-11-01>`_
|
||||
* cocoa: Drop iPhoto, Aperture and iTunes support. Was unmaintained and obsolete.
|
||||
* Drop "Audio Contents" scan type. Was confusing and seldom useful.
|
||||
* Change license to GPLv3
|
||||
|
||||
|
||||
3.9.1 (2014-10-17)
|
||||
----------------------
|
||||
|
||||
* Fixed ``AttributeError: 'ComboboxModel' object has no attribute 'reset'``. [Linux, Windows] (`#254 <https://github.com/hsoft/dupeguru/issues/254>`__)
|
||||
* Fixed ``PermissionError`` on saving results. (`#266 <https://github.com/hsoft/dupeguru/issues/266>`__)
|
||||
* Fixed a build problem introduced by Sphinx 1.2.3.
|
||||
* Updated German localisation, by Frank Weber.
|
||||
|
||||
|
||||
3.9.0 (2014-04-19)
|
||||
----------------------
|
||||
|
||||
* This is mostly a dependencies upgrade.
|
||||
* Upgraded to Python 3.3.
|
||||
* Upgraded to Qt 5.
|
||||
* Minimum Windows version is now Windows 7 64bit.
|
||||
* Minimum Ubuntu version is now 14.04.
|
||||
* Minimum OS X version is now 10.7 (Lion).
|
||||
* ... But with a couple of little improvements.
|
||||
* Improved documentation.
|
||||
* Overwrite subfolders' state when setting states in folder dialog (`#248 <https://github.com/hsoft/dupeguru/issues/248>`__)
|
||||
* The error report dialog now brings the user to Github issues.
|
||||
|
||||
|
||||
3.8.0 (2013-12-07)
|
||||
----------------------
|
||||
|
||||
* Disable symlink/hardlink deletion option when not relevant. (`#247 <https://github.com/hsoft/dupeguru/issues/247>`__)
|
||||
* Make Cmd+A select all folders in the Folder Selection dialog. [Mac] (`#228 <https://github.com/hsoft/dupeguru/issues/228>`__)
|
||||
* Make non-numeric delta comparison case insensitive. (`#239 <https://github.com/hsoft/dupeguru/issues/239>`__)
|
||||
* Fix surrogate-related UnicodeEncodeError on CSV export. (`#210 <https://github.com/hsoft/dupeguru/issues/210>`__)
|
||||
* Fixed crash on Dupe Count sorting with Delta + Dupes Only. (`#238 <https://github.com/hsoft/dupeguru/issues/238>`__)
|
||||
* Improved documentation.
|
||||
* Important internal refactorings.
|
||||
* Dropped Ubuntu 12.04 and 12.10 support.
|
||||
* Removed the fairware dialog (`More Info <http://www.hardcoded.net/articles/phasing-out-fairware>`__).
|
||||
|
||||
|
||||
3.7.1 (2013-08-19)
|
||||
----------------------
|
||||
|
||||
* Fixed folder scan type, which was broken in v3.7.0.
|
||||
|
||||
|
||||
3.7.0 (2013-08-17)
|
||||
----------------------
|
||||
|
||||
* Improved delta values to support non-numerical values. (`#213 <https://github.com/hsoft/dupeguru/issues/213>`__)
|
||||
* Improved the Re-Prioritize dialog's UI. (`#224 <https://github.com/hsoft/dupeguru/issues/224>`__)
|
||||
* Added hardlink/symlink support on Windows Vista+. (`#220 <https://github.com/hsoft/dupeguru/issues/220>`__)
|
||||
* Dropped 32bit support on Mac OS X.
|
||||
* Added Vietnamese localization by Phan Anh.
|
||||
|
||||
|
||||
3.6.1 (2013-04-28)
|
||||
----------------------
|
||||
|
||||
* Improved "Make Selection Reference" to make it clearer. (`#222 <https://github.com/hsoft/dupeguru/issues/222>`__)
|
||||
* Improved "Open Selected" to allow opening more than one file at once. (`#142 <https://github.com/hsoft/dupeguru/issues/142>`__)
|
||||
* Fixed a few typos here and there. (`#216 <https://github.com/hsoft/dupeguru/issues/216>`__ `#225 <https://github.com/hsoft/dupeguru/issues/225>`__)
|
||||
* Tweaked the fairware dialog (`More Info <http://www.hardcoded.net/articles/phasing-out-fairware>`__).
|
||||
* Added Arch Linux packaging
|
||||
* Added a 64-bit build for Windows.
|
||||
* Improved Russian localization by Kyrill Detinov.
|
||||
* Improved Brazilian localization by Victor Figueiredo.
|
||||
|
||||
|
||||
3.6.0 (2012-08-08)
|
||||
----------------------
|
||||
|
||||
* Added "Export to CSV". (`#189 <https://github.com/hsoft/dupeguru/issues/189>`__)
|
||||
* Added "Replace with symlinks" to complement "Replace with hardlinks". [Mac, Linux] (`#194 <https://github.com/hsoft/dupeguru/issues/194>`__)
|
||||
* dupeGuru now tells how many duplicates were affected after each re-prioritization operation. (`#204 <https://github.com/hsoft/dupeguru/issues/204>`__)
|
||||
* Added Longest/Shortest filename criteria in the re-prioritize dialog. (`#198 <https://github.com/hsoft/dupeguru/issues/198>`__)
|
||||
* Fixed result table cells which mistakenly became writable in v3.5.0. [Mac] (`#203 <https://github.com/hsoft/dupeguru/issues/203>`__)
|
||||
* Fixed "Rename Selected" which was broken since v3.5.0. [Mac] (`#202 <https://github.com/hsoft/dupeguru/issues/202>`__)
|
||||
* Fixed a bug where "Reset to Defaults" in the Columns menu wouldn't refresh menu items' marked state.
|
||||
* Added Brazilian localization by Victor Figueiredo.
|
||||
|
||||
|
||||
3.5.0 (2012-06-01)
|
||||
----------------------
|
||||
|
||||
* Added a Deletion Options panel.
|
||||
* Greatly improved memory usage for big scans.
|
||||
* Added a keybinding for the filter field. (`#182 <https://github.com/hsoft/dupeguru/issues/182>`__) [Mac]
|
||||
* Upgraded minimum requirements for Ubuntu to 12.04.
|
||||
|
||||
|
||||
3.4.1 (2012-04-14)
|
||||
----------------------
|
||||
|
||||
* Fixed the "Folders" scan type. [Mac]
|
||||
* Fixed localization issues. [Windows, Linux]
|
||||
|
||||
|
||||
3.4.0 (2012-03-29)
|
||||
----------------------
|
||||
|
||||
* Improved results window UI. [Windows, Linux]
|
||||
* Added a dialog to edit the Ignore List.
|
||||
* Added the ability to sort results by "marked" status.
|
||||
* Fixed "Open with default application". (`#190 <https://github.com/hsoft/dupeguru/issues/190>`__)
|
||||
* Fixed a bug where there would be a false reporting of discarded matches. (`#195 <https://github.com/hsoft/dupeguru/issues/195>`__)
|
||||
* Fixed various localization glitches.
|
||||
* Fixed hard crashes on crash reporting. (`#196 <https://github.com/hsoft/dupeguru/issues/196>`__)
|
||||
* Fixed bug where the details panel would show up at inconvenient places in the screen. [Windows, Linux]
|
||||
|
||||
|
||||
3.3.3 (2012-02-01)
|
||||
----------------------
|
||||
|
||||
* Fixed crash on adding some folders. [Mac OS X]
|
||||
* Added Ukrainian localization by Yuri Petrashko.
|
||||
|
||||
|
||||
3.3.2 (2012-01-16)
|
||||
----------------------
|
||||
|
||||
* Fixed random hard crashes (yeah, again). [Mac OS X]
|
||||
* Fixed crash on Export to HTML. [Windows, Linux]
|
||||
* Added Armenian localization by Hrant Ohanyan.
|
||||
* Added Russian localization by Igor Pavlov.
|
||||
|
||||
|
||||
3.3.1 (2011-12-02)
|
||||
----------------------
|
||||
|
||||
* Fixed a couple of nasty crashes.
|
||||
|
||||
|
||||
3.3.0 (2011-11-30)
|
||||
----------------------
|
||||
|
||||
* Added multiple-selection in folder selection dialog for a more efficient folder removal. (`#179 <https://github.com/hsoft/dupeguru/issues/179>`__)
|
||||
* Fixed a crash in the prioritize dialog. (`#178 <https://github.com/hsoft/dupeguru/issues/178>`__)
|
||||
* Fixed a bug where mass marking with a filter would mark more than filtered duplicates. (`#181 <https://github.com/hsoft/dupeguru/issues/181>`__)
|
||||
* Fixed random hard crashes. [Mac OS X] (`#183 <https://github.com/hsoft/dupeguru/issues/183>`__ `#184 <https://github.com/hsoft/dupeguru/issues/184>`__)
|
||||
* Added Czech localization by Aleš Nehyba.
|
||||
* Added Italian localization by Paolo Rossi.
|
||||
|
||||
|
||||
3.2.1 (2011-10-02)
|
||||
----------------------
|
||||
|
||||
* Fixed a couple of broken action bindings from v3.2.0.
|
||||
|
||||
|
||||
3.2.0 (2011-09-27)
|
||||
----------------------
|
||||
|
||||
* Added duplicate re-prioritization dialog. (`#138 <https://github.com/hsoft/dupeguru/issues/138>`__)
|
||||
* Added font size preference for duplicate table. (`#82 <https://github.com/hsoft/dupeguru/issues/82>`__)
|
||||
* Added Quicklook support. [Mac OS X] (`#21 <https://github.com/hsoft/dupeguru/issues/21>`__)
|
||||
* Improved behavior of Mark Selected. (`#139 <https://github.com/hsoft/dupeguru/issues/139>`__)
|
||||
* Improved filename sorting. (`#169 <https://github.com/hsoft/dupeguru/issues/169>`__)
|
||||
* Added Chinese (Simplified) localization by Eric Dee.
|
||||
* Tweaked the fairware system.
|
||||
* Upgraded minimum requirements to OS X 10.6 and Ubuntu 11.04.
|
||||
|
||||
|
||||
3.1.2 (2011-08-25)
|
||||
----------------------
|
||||
|
||||
* Fixed a bug preventing the Folders scan from working. (`#172 <https://github.com/hsoft/dupeguru/issues/172>`__)
|
||||
|
||||
|
||||
3.1.1 (2011-08-24)
|
||||
----------------------
|
||||
|
||||
* Added German localization by Gregor Tätzner.
|
||||
* Improved OS X Lion compatibility. [Mac OS X]
|
||||
* Made the file collection phase cancellable. (`#168 <https://github.com/hsoft/dupeguru/issues/168>`__)
|
||||
* Fixed glitch in folder window upon selecting a folder state. [Windows, Linux] (`#165 <https://github.com/hsoft/dupeguru/issues/165>`__)
|
||||
* Fixed a text coloring glitch in the results. (`#156 <https://github.com/hsoft/dupeguru/issues/156>`__)
|
||||
* Fixed glitch in the sorting feature of the Folder column. (`#161 <https://github.com/hsoft/dupeguru/issues/161>`__)
|
||||
* Make sure that saved results have the ".dupeguru" extension. [Linux] (`#157 <https://github.com/hsoft/dupeguru/issues/157>`__)
|
||||
|
||||
|
||||
3.1.0 (2011-04-16)
|
||||
----------------------
|
||||
|
||||
* Added the "Folders" scan type. (`#89 <https://github.com/hsoft/dupeguru/issues/89>`__)
|
||||
* Fixed a couple of crashes. (`#140 <https://github.com/hsoft/dupeguru/issues/140>`__ `#149 <https://github.com/hsoft/dupeguru/issues/149>`__)
|
||||
|
||||
|
||||
3.0.2 (2011-03-16)
|
||||
----------------------
|
||||
|
||||
* Fixed crash after removing marked dupes. (`#140 <https://github.com/hsoft/dupeguru/issues/140>`__)
|
||||
* Fixed crash on error handling. [Windows] (`#144 <https://github.com/hsoft/dupeguru/issues/144>`__)
|
||||
* Fixed crash on copy/move. [Windows] (`#148 <https://github.com/hsoft/dupeguru/issues/148>`__)
|
||||
* Fixed crash when launching dupeGuru from a very long folder name. [Mac OS X] (`#119 <https://github.com/hsoft/dupeguru/issues/119>`__)
|
||||
* Fixed a refresh bug in directory panel. (`#153 <https://github.com/hsoft/dupeguru/issues/153>`__)
|
||||
* Improved reliability of the "Send to Trash" operation. [Linux]
|
||||
* Tweaked Fairware reminders.
|
||||
|
||||
|
||||
3.0.1 (2011-01-27)
|
||||
----------------------
|
||||
|
||||
* Restored the context menu which had been broken in 3.0.0. [Mac OS X] (`#133 <https://github.com/hsoft/dupeguru/issues/133>`__)
|
||||
* Fixed a bug where an "unsaved results" warning would be issued on quit even with empty results. (`#134 <https://github.com/hsoft/dupeguru/issues/134>`__)
|
||||
* Removed focus from the cancel button in the progress dialog to avoid accidental cancellations. [Mac OS X] (`#135 <https://github.com/hsoft/dupeguru/issues/135>`__)
|
||||
* Folders added through drag and drop are added to the recent folders list. (`#136 <https://github.com/hsoft/dupeguru/issues/136>`__)
|
||||
* Added a debugging mode. (`#132 <https://github.com/hsoft/dupeguru/issues/132>`__)
|
||||
* Fixed french localization glitches.
|
||||
|
||||
|
||||
3.0.0 (2011-01-24)
|
||||
----------------------
|
||||
|
||||
* Re-designed the UI. (`#129 <https://github.com/hsoft/dupeguru/issues/129>`__)
|
||||
* Internationalized dupeGuru and localized it to french. (`#32 <https://github.com/hsoft/dupeguru/issues/32>`__)
|
||||
* Changed the format of the help file. (`#130 <https://github.com/hsoft/dupeguru/issues/130>`__)
|
||||
|
||||
|
||||
2.12.3 (2011-01-01)
|
||||
----------------------
|
||||
|
||||
* Fixed bug causing results to be corrupted after a scan cancellation. (`#120 <https://github.com/hsoft/dupeguru/issues/120>`__)
|
||||
* Fixed crash when fetching Fairware unpaid hours. (`#121 <https://github.com/hsoft/dupeguru/issues/121>`__)
|
||||
* Fixed crash when replacing files with hardlinks. (`#122 <https://github.com/hsoft/dupeguru/issues/122>`__)
|
||||
|
||||
|
||||
2.12.2 (2010-10-05)
|
||||
----------------------
|
||||
|
||||
* Fixed delta column colors which were broken since 2.12.0.
|
||||
* Fixed column sorting crash. (`#108 <https://github.com/hsoft/dupeguru/issues/108>`__)
|
||||
* Fixed occasional crash during scan. (`#106 <https://github.com/hsoft/dupeguru/issues/106>`__)
|
||||
|
||||
|
||||
2.12.1 (2010-09-30)
|
||||
----------------------
|
||||
|
||||
* Re-licensed dupeGuru to BSD and made it `Fairware <http://open.hardcoded.net/about/>`__.
|
||||
|
||||
|
||||
2.12.0 (2010-09-26)
|
||||
----------------------
|
||||
|
||||
* Improved UI with a little revamp.
|
||||
* Added the possibility to place hardlinks to references after having deleted duplicates. [Mac OS X, Linux] (`#91 <https://github.com/hsoft/dupeguru/issues/91>`__)
|
||||
* Added an option to ignore duplicates hardlinking to the same file. [Mac OS X, Linux] (`#92 <https://github.com/hsoft/dupeguru/issues/92>`__)
|
||||
* Added multiple selection in the "Add Directory" dialog. [Mac OS X] (`#105 <https://github.com/hsoft/dupeguru/issues/105>`__)
|
||||
* Fixed a bug preventing drag & drop from working in the Directories panel. [Windows, Linux]
|
||||
|
||||
|
||||
2.11.1 (2010-08-26)
|
||||
----------------------
|
||||
|
||||
* Fixed HTML exporting which was broken in 2.11.0.
|
||||
|
||||
|
||||
2.11.0 (2010-08-18)
|
||||
----------------------
|
||||
|
||||
* Added the ability to save results (and reload them) at arbitrary locations.
|
||||
* Improved the way reference files in dupe groups are chosen. (`#15 <https://github.com/hsoft/dupeguru/issues/15>`__)
|
||||
* Remember size/position of all windows between launches. (`#102 <https://github.com/hsoft/dupeguru/issues/102>`__)
|
||||
* Fixed a bug sometimes preventing dupeGuru from reloading previous results.
|
||||
* Fixed a bug sometimes causing the progress dialog to be stuck there. [Mac OS X] (`#103 <https://github.com/hsoft/dupeguru/issues/103>`__)
|
||||
* Removed the Creation Date column, which wasn't displaying the correct value anyway. (`#101 <https://github.com/hsoft/dupeguru/issues/101>`__)
|
||||
|
||||
|
||||
2.10.1 (2010-07-15)
|
||||
----------------------
|
||||
|
||||
* Fixed a couple of crashes. (`#95 <https://github.com/hsoft/dupeguru/issues/95>`__, `#97 <https://github.com/hsoft/dupeguru/issues/97>`__, `#100 <https://github.com/hsoft/dupeguru/issues/100>`__)
|
||||
|
||||
|
||||
2.10.0 (2010-04-13)
|
||||
----------------------
|
||||
|
||||
* Improved error messages when files can't be sent to trash, moved or copied.
|
||||
* Added a custom command invocation action. (`#12 <https://github.com/hsoft/dupeguru/issues/12>`__)
|
||||
* Filters are now applied on whole paths. (`#4 <https://github.com/hsoft/dupeguru/issues/4>`__)
|
||||
|
||||
|
||||
2.9.2 (2010-02-10)
|
||||
----------------------
|
||||
|
||||
* dupeGuru is now 64-bit on Mac OS X!
|
||||
* Fixed a crash upon quitting when support folder is not present. (`#83 <https://github.com/hsoft/dupeguru/issues/83>`__)
|
||||
* Fixed a crash during sorting. (`#85 <https://github.com/hsoft/dupeguru/issues/85>`__)
|
||||
* Fixed selection glitches, especially while renaming. (`#93 <https://github.com/hsoft/dupeguru/issues/93>`__)
|
||||
|
||||
|
||||
2.9.1 (2010-01-13)
|
||||
----------------------
|
||||
|
||||
* Improved memory usage for Contents scans. (`#75 <https://github.com/hsoft/dupeguru/issues/75>`__)
|
||||
* Improved scanning speed when ref directories are involved. (`#77 <https://github.com/hsoft/dupeguru/issues/77>`__)
|
||||
* Show a message dialog at the end of the scan if no duplicates are found. (`#81 <https://github.com/hsoft/dupeguru/issues/81>`__)
|
||||
* Fixed a bug sometimes causing the small files threshold pref to be ignored. [Mac OS X] (`#75 <https://github.com/hsoft/dupeguru/issues/75>`__)
|
||||
|
||||
|
||||
2.9.0 (2009-11-03)
|
||||
----------------------
|
||||
|
||||
* Significantly improved speed and memory usage of big contents-based scans.
|
||||
* Added drag & drop support in the Directories panel. (`#9 <https://github.com/hsoft/dupeguru/issues/9>`__)
|
||||
* Fixed a bug causing dupeGuru to be confused if a scanned file was moved during the scan. (`#72 <https://github.com/hsoft/dupeguru/issues/72>`__)
|
||||
* Dropped support for Mac OS X 10.4 (Tiger)
|
||||
|
||||
|
||||
2.8.2 (2009-10-14)
|
||||
----------------------
|
||||
|
||||
* Improved directory selection in the Directories panel (Windows). (`#56 <https://github.com/hsoft/dupeguru/issues/56>`__)
|
||||
* Fixed a bug preventing dupeGuru from starting on certain machines (Windows). (`#68 <https://github.com/hsoft/dupeguru/issues/68>`__)
|
||||
* Fixed a crash during very big scans. (`#70 <https://github.com/hsoft/dupeguru/issues/70>`__)
|
||||
|
||||
|
||||
2.8.1 (2009-10-02)
|
||||
----------------------
|
||||
|
||||
* Fixed crash with filtering when regular expressions were enabled. (`#60 <https://github.com/hsoft/dupeguru/issues/60>`__)
|
||||
* Fixed crash when setting directories' state. (Mac OS X) (`#66 <https://github.com/hsoft/dupeguru/issues/66>`__)
|
||||
* Fixed crash with Make Reference when certain filters are applied. (Mac OS X) (`#55 <https://github.com/hsoft/dupeguru/issues/55>`__)
|
||||
* Improved error handling during delete/move/copy actions. (`#62 <https://github.com/hsoft/dupeguru/issues/62>`__ `#65 <https://github.com/hsoft/dupeguru/issues/65>`__)
|
||||
|
||||
|
||||
2.8.0 (2009-09-07)
|
||||
----------------------
|
||||
|
||||
* Added support for all kinds of bundle (not just applications) (Mac OS X) (`#11 <https://github.com/hsoft/dupeguru/issues/11>`__)
|
||||
* Re-introduced the Export to XHTML feature to Windows. (`#14 <https://github.com/hsoft/dupeguru/issues/14>`__)
|
||||
* Improved Export to XHTML speed. (`#14 <https://github.com/hsoft/dupeguru/issues/14>`__)
|
||||
* Improved Contents scanning speed for large files. (`#33 <https://github.com/hsoft/dupeguru/issues/33>`__)
|
||||
* Improved the grouping algorithm to reduce the number of discarded files in non-exact scans. (`#51 <https://github.com/hsoft/dupeguru/issues/51>`__)
|
||||
* Stopped showing the same file on the 2 sides of the details panel when a ref file is selected. (`#50 <https://github.com/hsoft/dupeguru/issues/50>`__)
|
||||
* Fixed crashes in the Directories panel. (`#46 <https://github.com/hsoft/dupeguru/issues/46>`__)
|
||||
|
||||
|
||||
2.7.3 (2009-06-20)
|
||||
----------------------
|
||||
|
||||
* Fixed bugs with selection being jumpy during "Make Reference" actions and Power Marker
|
||||
switches. (`#3 <https://github.com/hsoft/dupeguru/issues/3>`__)
|
||||
* Fixed crash happening when a file with non-roman characters couldn't be analyzed. (`#30 <https://github.com/hsoft/dupeguru/issues/30>`__)
|
||||
* Fixed crash sometimes happening during the file collection phase in scanning. (`#38 <https://github.com/hsoft/dupeguru/issues/38>`__)
|
||||
* Restored double-click and right-click behavior lost in the PyQt move (Windows). (`#34 <https://github.com/hsoft/dupeguru/issues/34>`__ `#35 <https://github.com/hsoft/dupeguru/issues/35>`__)
|
||||
|
||||
|
||||
2.7.2 (2009-06-10)
|
||||
----------------------
|
||||
|
||||
* Fixed an occasional crash on Copy/Move operations. (`#16 <https://github.com/hsoft/dupeguru/issues/16>`__)
|
||||
* Added automatic exclusion for sensible folders (like system folders). (`#20 <https://github.com/hsoft/dupeguru/issues/20>`__)
|
||||
* Fixed an occasional crash when application files were part of the results (Mac OS X). (`#25 <https://github.com/hsoft/dupeguru/issues/25>`__)
|
||||
|
||||
|
||||
2.7.1 (2009-05-29)
|
||||
----------------------
|
||||
|
||||
* Fixed a bug causing crashes when having application files in the results.
|
||||
* Fixed a bug causing a GUI freeze at the beginning of a scan with a lot of files.
|
||||
* Fixed a bug that sometimes caused a crash when an action was cancelled, and then started again.
|
||||
|
||||
|
||||
2.7.0 (2009-05-25)
|
||||
----------------------
|
||||
|
||||
* Converted the Windows GUI to Qt.
|
||||
* Improved the reliability of the scanning process.
|
||||
|
||||
|
||||
2.6.1 (2009-03-27)
|
||||
----------------------
|
||||
|
||||
* **Fixed** an occasional crash caused by permission issues.
|
||||
* **Fixed** a bug where the "X discarded" notice would show a too large number of discarded
|
||||
duplicates.
|
||||
|
||||
|
||||
2.6.0 (2008-09-10)
|
||||
----------------------
|
||||
|
||||
* **Added** a small file threshold preference.
|
||||
* **Added** a notice in the status bar when matches were discarded during the scan.
|
||||
* **Improved** duplicate prioritization (smartly chooses which file you will keep).
|
||||
* **Improved** scan progress feedback.
|
||||
* **Improved** responsiveness of the user interface for certain actions.
|
||||
|
||||
|
||||
2.5.4 (2008-08-10)
|
||||
----------------------
|
||||
|
||||
* **Improved** the speed of results loading and saving.
|
||||
* **Fixed** a crash sometimes occurring during duplicate deletion.
|
||||
|
||||
|
||||
2.5.3 (2008-07-08)
|
||||
----------------------
|
||||
|
||||
* **Improved** unicode handling for filenames. dupeGuru will now find a lot more duplicates if your files have non-ascii characters in it.
|
||||
* **Fixed** "Clear Ignore List" crash in Windows.
|
||||
|
||||
|
||||
2.5.2 (2008-01-10)
|
||||
----------------------
|
||||
|
||||
* **Improved** the handling of low memory situations.
|
||||
* **Improved** the directory panel. The "Remove" button changes to "Put Back" when an excluded directory is selected.
|
||||
* **Improved** scan, delete and move speed in situations where there were a lot of duplicates.
|
||||
* **Fixed** occasional crashes when moving bundles (such as .app files).
|
||||
* **Fixed** occasional crashes when moving a lot of files at once.
|
||||
|
||||
|
||||
2.5.1 (2007-11-22)
|
||||
----------------------
|
||||
|
||||
* **Added** the "Remove empty folders" option.
|
||||
* **Fixed** results load/save issues.
|
||||
* **Fixed** occasional status bar inaccuracies when the results are filtered.
|
||||
|
||||
|
||||
2.5.0 (2007-09-15)
|
||||
----------------------
|
||||
|
||||
* **Added** post scan filtering.
|
||||
* **Fixed** issues with the rename feature under Windows
|
||||
* **Fixed** some user interface annoyances under Windows
|
||||
|
||||
|
||||
2.4.8 (2007-04-14)
|
||||
----------------------
|
||||
|
||||
* **Improved** UI responsiveness (using threads) under Mac OS X.
|
||||
* **Improved** result load/save speed and memory usage.
|
||||
|
||||
|
||||
2.4.7 (2007-03-10)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a "bad file descriptor" error occasionally popping up.
|
||||
* **Fixed** a bug with non-latin directory names.
|
||||
|
||||
|
||||
2.4.6 (2007-02-10)
|
||||
----------------------
|
||||
|
||||
* **Added** Re-orderable columns. In fact, I re-added the feature which was lost in the C# conversion in 2.4.0 (Windows).
|
||||
* **Changed** the behavior of the scanning engine when setting the hardness to 100. It will now only match files that have their words in the same order.
|
||||
* **Fixed** a bug with all the Delete/Move/Copy actions with certain kinds of files.
|
||||
|
||||
|
||||
2.4.5 (2007-01-11)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a bug with the Move action.
|
||||
|
||||
|
||||
2.4.4 (2007-01-07)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a "ghosting" bug. Dupes deleted by dupeGuru would sometimes come back in subsequent scans (Windows).
|
||||
* **Fixed** bugs sometimes making dupeGuru crash when marking a dupe (Windows).
|
||||
* **Fixed** some minor visual glitches (Windows).
|
||||
|
||||
|
||||
2.4.3 (2006-12-08)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a mishandling of ".app" files (OS X).
|
||||
* **Fixed** a bug preventing files from "reference" directories to be displayed in blue in the results (Windows).
|
||||
* **Fixed** a bug preventing some files to be sent to the recycle bin (Windows).
|
||||
* **Fixed** a bug in the packaging preventing certain Windows configurations to start dupeGuru at all.
|
||||
|
||||
|
||||
2.4.2 (2006-11-18)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a bug with directory states.
|
||||
|
||||
|
||||
2.4.1 (2006-11-15)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a bug causing the ignore list not to be saved.
|
||||
* **Fixed** a bug sometimes making delete and move operations stall.
|
||||
|
||||
|
||||
2.4.0 (2006-11-10)
|
||||
----------------------
|
||||
|
||||
* **Changed** the Windows interface. It is now .NET based.
|
||||
* **Added** an auto-update feature to the windows version.
|
||||
* **Changed** the way power marking works. It is now a mode instead of a separate window.
|
||||
* **Changed** the "Size (MB)" column for a "Size (KB)" column. The values are now "ceiled" instead of rounded. Therefore, a size "0" is now really 0 bytes, not just a value too small to be rounded up. It is also the case for delta values.
|
||||
* **Removed** the min word length/count options. These came from Mp3 Filter, and just aren't used anymore. Word weighting does pretty much the same job.
|
||||
|
||||
|
||||
2.3.4 (2006-11-07)
|
||||
----------------------
|
||||
|
||||
* **Improved** speed and memory usage of the scanning engine, again. Does it mean there was a lot of improvements to be made? Nah...
|
||||
|
||||
|
||||
2.3.3 (2006-11-02)
|
||||
----------------------
|
||||
|
||||
* **Improved** speed and memory usage of the scanning engine, especially when the scan results in a lot of duplicates.
|
||||
* Now I wonder if Sparkle is going to work well...
|
||||
|
||||
|
||||
2.3.2 (2006-10-16)
|
||||
----------------------
|
||||
|
||||
* **Added** an auto-update feature in the Mac OS X version (with Sparkle).
|
||||
* **Fixed** a bug preventing some duplicate reports to be created correctly under Windows.
|
||||
|
||||
|
||||
2.3.1 (2006-10-02)
|
||||
----------------------
|
||||
|
||||
* **Fixed** a bug preventing some duplicates to be found, especially when scanning lots of files.
|
||||
|
||||
|
||||
2.3.0 (2006-09-22)
|
||||
----------------------
|
||||
|
||||
* **Added** XHTML export feature.
|
||||
|
||||
|
||||
2.2.10 (2006-08-31)
|
||||
----------------------
|
||||
|
||||
* **Added** sticky columns.
|
||||
* **Fixed** an issue with file caching between scans.
|
||||
* **Fixed** an issue preventing some duplicates from being deleted/moved/copied.
|
||||
|
||||
|
||||
2.2.9 (2006-08-27)
|
||||
----------------------
|
||||
|
||||
* **Fixed** an issue with ignore list and unicode.
|
||||
* **Fixed** an issue with file attribute fetching sometimes causing dupeGuru to crash.
|
||||
* **Fixed** an issue in the directories panel under Windows.
|
||||
|
||||
|
||||
2.2.8 (2006-08-17)
|
||||
----------------------
|
||||
|
||||
* **Fixed** an issue in the duplicate seeking engine preventing some duplicates to be found.
|
||||
|
||||
|
||||
2.2.7 (2006-08-12)
|
||||
----------------------
|
||||
|
||||
* **Improved** unicode support.
|
||||
* **Improved** the "Reveal in Finder" ("Open Containing Folder" in Windows) feature so it selects the file in the folder it opens.
|
||||
|
||||
|
||||
2.2.6 (2006-08-07)
|
||||
----------------------
|
||||
|
||||
* **Improved** the ignore list system.
|
||||
* dupeGuru is now a Universal application on Mac OS X.
|
||||
|
||||
|
||||
2.2.5 (2006-07-26)
|
||||
----------------------
|
||||
|
||||
* **Improved** application (.app) dupe detection on Mac OS X.
|
||||
* **Fixed** an issue that occasionally made dupeGuru crash on startup.
|
||||
|
||||
|
||||
2.2.4 (2006-06-27)
|
||||
----------------------
|
||||
|
||||
* **Fixed** an issue with Move and Copy features.
|
||||
|
||||
|
||||
2.2.3 (2006-06-15)
|
||||
----------------------
|
||||
|
||||
* **Improved** duplicate scanning speed.
|
||||
* **Added** a warning that a file couldn't be renamed if a file with the same name already exists.
|
||||
|
||||
|
||||
2.2.2 (2006-06-07)
|
||||
----------------------
|
||||
|
||||
* **Added** "Rename Selected" feature.
|
||||
* **Fixed** some minor issues with "Reload Last Results" feature.
|
||||
* **Fixed** ignore list issues.
|
||||
|
||||
|
||||
2.2.1 (2006-05-22)
|
||||
----------------------
|
||||
|
||||
* **Fixed** occasional progress bar woes under Windows.
|
||||
* **Fixed** a bug in the registration system under Windows.
|
||||
* Nothing has been changed in the Mac OS X version, but I want to keep version in sync.
|
||||
|
||||
|
||||
2.2.0 (2006-05-10)
|
||||
----------------------
|
||||
|
||||
* **Added** destination path re-creation options.
|
||||
* **Added** an ignore list.
|
||||
* **Changed** the main icon.
|
||||
* **Improved** dramatically the delta values feature.
|
||||
|
||||
|
||||
2.1.2 (2006-04-18)
|
||||
----------------------
|
||||
|
||||
* **Added** the "Match similar words" option.
|
||||
* **Fixed** Power marking issues under Mac.
|
||||
|
||||
|
||||
2.1.1 (2006-04-14)
|
||||
----------------------
|
||||
|
||||
* **Added** the "Display delta values" option.
|
||||
* **Improved** Power marking sorting speed under Mac.
|
||||
* **Fixed** Power marking sorting issues.
|
||||
|
||||
|
||||
2.1.0 (2006-04-03)
|
||||
----------------------
|
||||
|
||||
* **Added** the Power Marker feature.
|
||||
* **Fixed** a column sorting bug. The results would sometimes lose their sort order.
|
||||
* **Fixed** a bug with the Make Reference feature. The results sometimes wasn't correctly refreshed after the reference switch.
|
||||
|
||||
|
||||
2.0.1 (2006-03-23)
|
||||
----------------------
|
||||
|
||||
* **Fixed** an issue occasionally occurring when trying to reload results from removable media that is no longer present.
|
||||
|
||||
|
||||
2.0.0 (2006-03-17)
|
||||
----------------------
|
||||
|
||||
* Complete rewrite.
|
||||
* Now runs on Mac OS X.
|
||||
|
||||
|
||||
1.0.0 (2004-09-24)
|
||||
----------------------
|
||||
|
||||
* Initial release.
|
||||
|
||||
91
help/en/_sources/contribute.rst.txt
Normal file
@@ -0,0 +1,91 @@
|
||||
Contribute to dupeGuru
|
||||
======================
|
||||
|
||||
dupeGuru was started as shareware (thus proprietary) so it doesn't have a legacy of
|
||||
community-building. It's `been open source`_ for a while now and, although I've ("I" being Virgil
|
||||
Dupras, author of the software) always wanted to have people other than me working on dupeGuru, I've
|
||||
failed at attracting them.
|
||||
|
||||
Since the end of 2013, I've been putting a lot of efforts into dupeGuru's
|
||||
:doc:`developer documentation </developer/index>` and I'm more serious about my commitment to create
|
||||
a community around this project.
|
||||
|
||||
So, whatever your skills, if you're interested in contributing to dupeGuru, please do so. Normally,
|
||||
this documentation should be enough to get you started, but if it isn't, then **please**,
|
||||
`let me know`_ because it's a problem that I'm committed to fix. If there's any situation where you'd
|
||||
wish to contribute but some doubt you're having prevent you from going forward, please contact me.
|
||||
I'd much prefer to spend the time figuring out with you whether (and how) you can contribute than
|
||||
taking the chance of missing that opportunity.
|
||||
|
||||
Development process
|
||||
-------------------
|
||||
|
||||
* `Source code repository`_
|
||||
* `Issue Tracker`_
|
||||
* `Issue labels meaning`_
|
||||
|
||||
dupeGuru's source code is on Github and thus managed in a Git repository. At all times, you should
|
||||
be able to build from source a fresh checkout of the ``master`` branch using instructions from the
|
||||
``README.md`` file at the root of this project. If you can't, it's a bug. Please report it.
|
||||
|
||||
``master`` is the main development branch, and thus represents what going to be included in the
|
||||
next feature release. When needed, we create maintenance branches for bugfixes of the current
|
||||
feature release.
|
||||
|
||||
When implementing a big feature, it's possible that it gets its own branch until
|
||||
it's stable enough to merge into ``master``.
|
||||
|
||||
Every release is tagged, the tag name containing the edition (for old versions) and its version.
|
||||
For example, release 6.6.0 of dupeGuru ME is tagged ``me6.6.0``. Newer releases are tagged only
|
||||
with the version number (because editions don't exist anymore), for example ``4.0.0``.
|
||||
|
||||
Once you're past building the software, the :doc:`developer documentation </developer/index>` should
|
||||
be enough to get you started with actual development. Then again, proper documentation is a very
|
||||
difficult task and, in the case of dupeGuru, this documentation was practically nonexistent until
|
||||
late in the project, so it's still lacking.
|
||||
|
||||
However, I'm committed to fix this situation, so if you're in a situation where you lack proper
|
||||
documentation to figure something out about this code, please contact me.
|
||||
|
||||
Tasks for non-developers
|
||||
------------------------
|
||||
|
||||
**Create and comment issues**. The single most useful way for a user who is not a developer to
|
||||
contribute to a software project is by thoroughly documenting a bug or a feature request. Most of
|
||||
the time, what we get as developers are emails like "the app crashes" and we spend a lot of time
|
||||
trying to figure out the cause of that bug. By properly describing the nature and context of a crash
|
||||
(we learn to do that with experience as a user who reports bugs), you help developers so immensely,
|
||||
you have no idea.
|
||||
|
||||
It's the same thing with feature requests. Description of a feature request, when thoughts have
|
||||
already been given to how such a feature would fit in the current design, are precious to developers
|
||||
and help them figure out a clear roadmap for the project.
|
||||
|
||||
So, even if you're not a developer, you can always open a Github account and create/comment issues.
|
||||
Your contribution will be much appreciated.
|
||||
|
||||
**Documentation**. This is a bit trickier because dupeGuru's documentation is written with a rather
|
||||
complex markup language, `Sphinx`_ (based on `reST`_). To properly work within the documentation,
|
||||
you have to know that language. I don't think that learning this language is outside the realm of
|
||||
possibility for a non-developer, but it might be a daunting task.
|
||||
|
||||
That being said, if it's a minor modification to the documentation, nothing stops you from opening
|
||||
an issue (there's a label for documentation issues, so this kind of issue is relevant to the
|
||||
tracker) describing the change you propose to make and I'll be happy to make the change myself (if
|
||||
relevant, of course).
|
||||
|
||||
Even if it's a bigger contribution to the documentation you want to make, I probably wouldn't mind
|
||||
doing the formatting myself. But in that case, it's better to contact me first to make sure that we
|
||||
agree on what should be added to the documentation.
|
||||
|
||||
**Translation**. Creating or improving an existing translation is a very good way to contribute to
|
||||
dupeGuru. For more information about how to do that, you can refer to the `translator guide`_.
|
||||
|
||||
.. _been open source: https://www.hardcoded.net/articles/free-as-in-speech-fair-as-in-trade
|
||||
.. _let me know: mailto:hsoft@hardcoded.net
|
||||
.. _Source code repository: https://github.com/hsoft/dupeguru
|
||||
.. _Issue Tracker: https://github.com/hsoft/dupeguru/issues
|
||||
.. _Issue labels meaning: https://github.com/hsoft/dupeguru/wiki/issue-labels
|
||||
.. _Sphinx: http://sphinx-doc.org/
|
||||
.. _reST: http://en.wikipedia.org/wiki/ReStructuredText
|
||||
.. _translator guide: https://github.com/hsoft/dupeguru/wiki/Translator-Guide
|
||||
5
help/en/_sources/developer/core/app.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
core.app
|
||||
========
|
||||
|
||||
.. automodule:: core.app
|
||||
:members:
|
||||
5
help/en/_sources/developer/core/directories.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
core.directories
|
||||
================
|
||||
|
||||
.. automodule:: core.directories
|
||||
:members:
|
||||
36
help/en/_sources/developer/core/engine.rst.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
core.engine
|
||||
===========
|
||||
|
||||
.. automodule:: core.engine
|
||||
|
||||
.. autoclass:: Match
|
||||
|
||||
.. autoclass:: Group
|
||||
:members:
|
||||
|
||||
.. autofunction:: build_word_dict
|
||||
.. autofunction:: compare
|
||||
.. autofunction:: compare_fields
|
||||
.. autofunction:: getmatches
|
||||
.. autofunction:: getmatches_by_contents
|
||||
.. autofunction:: get_groups
|
||||
.. autofunction:: merge_similar_words
|
||||
.. autofunction:: reduce_common_words
|
||||
|
||||
.. _fields:
|
||||
|
||||
Fields
|
||||
------
|
||||
|
||||
Fields are groups of words which each represent a significant part of the whole name. This concept
|
||||
is sifnificant in music file names, where we often have names like "My Artist - a very long title
|
||||
with many many words".
|
||||
|
||||
This title has 10 words. If you run as scan with a bit of tolerance, let's say 90%, you'll be able
|
||||
to find a dupe that has only one "many" in the song title. However, you would also get false
|
||||
duplicates from a title like "My Giraffe - a very long title with many many words", which is of
|
||||
course a very different song and it doesn't make sense to match them.
|
||||
|
||||
When matching by fields, each field (separated by "-") is considered as a separate string to match
|
||||
independently. After all fields are matched, the lowest result is kept. In the "Giraffe" example we
|
||||
gave, the result would be 50% instead of 90% in normal mode.
|
||||
5
help/en/_sources/developer/core/fs.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
core.fs
|
||||
=======
|
||||
|
||||
.. automodule:: core.fs
|
||||
:members:
|
||||
@@ -0,0 +1,5 @@
|
||||
core.gui.deletion_options
|
||||
=========================
|
||||
|
||||
.. automodule:: core.gui.deletion_options
|
||||
:members:
|
||||
10
help/en/_sources/developer/core/gui/index.rst.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
core.gui
|
||||
========
|
||||
|
||||
.. automodule:: core.gui
|
||||
:members:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
deletion_options
|
||||
12
help/en/_sources/developer/core/index.rst.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
core
|
||||
====
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
app
|
||||
fs
|
||||
engine
|
||||
directories
|
||||
results
|
||||
gui/index
|
||||
5
help/en/_sources/developer/core/results.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
core.results
|
||||
============
|
||||
|
||||
.. automodule:: core.results
|
||||
:members:
|
||||
5
help/en/_sources/developer/hscommon/build.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
hscommon.build
|
||||
==============
|
||||
|
||||
.. automodule:: hscommon.build
|
||||
:members:
|
||||
5
help/en/_sources/developer/hscommon/conflict.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
hscommon.conflict
|
||||
=================
|
||||
|
||||
.. automodule:: hscommon.conflict
|
||||
:members:
|
||||
5
help/en/_sources/developer/hscommon/desktop.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
hscommon.desktop
|
||||
================
|
||||
|
||||
.. automodule:: hscommon.desktop
|
||||
:members:
|
||||
12
help/en/_sources/developer/hscommon/gui/base.rst.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
hscommon.gui.base
|
||||
=================
|
||||
|
||||
.. automodule:: hscommon.gui.base
|
||||
|
||||
.. autosummary::
|
||||
|
||||
GUIObject
|
||||
|
||||
.. autoclass:: GUIObject
|
||||
:members:
|
||||
:private-members:
|
||||
25
help/en/_sources/developer/hscommon/gui/column.rst.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
hscommon.gui.column
|
||||
============================
|
||||
|
||||
.. automodule:: hscommon.gui.column
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Columns
|
||||
Column
|
||||
ColumnsView
|
||||
PrefAccessInterface
|
||||
|
||||
.. autoclass:: Columns
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: Column
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: ColumnsView
|
||||
:members:
|
||||
|
||||
.. autoclass:: PrefAccessInterface
|
||||
:members:
|
||||
@@ -0,0 +1,18 @@
|
||||
hscommon.gui.progress_window
|
||||
============================
|
||||
|
||||
.. automodule:: hscommon.gui.progress_window
|
||||
|
||||
.. autosummary::
|
||||
|
||||
ProgressWindow
|
||||
ProgressWindowView
|
||||
|
||||
.. autoclass:: ProgressWindow
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: ProgressWindowView
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
hscommon.gui.selectable_list
|
||||
============================
|
||||
|
||||
.. automodule:: hscommon.gui.selectable_list
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Selectable
|
||||
SelectableList
|
||||
GUISelectableList
|
||||
GUISelectableListView
|
||||
|
||||
.. autoclass:: Selectable
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: SelectableList
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: GUISelectableList
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: GUISelectableListView
|
||||
:members:
|
||||
26
help/en/_sources/developer/hscommon/gui/table.rst.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
hscommon.gui.table
|
||||
==================
|
||||
|
||||
.. automodule:: hscommon.gui.table
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Table
|
||||
Row
|
||||
GUITable
|
||||
GUITableView
|
||||
|
||||
.. autoclass:: Table
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: Row
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: GUITable
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: GUITableView
|
||||
:members:
|
||||
16
help/en/_sources/developer/hscommon/gui/text_field.rst.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
hscommon.gui.text_field
|
||||
=======================
|
||||
|
||||
.. automodule:: hscommon.gui.text_field
|
||||
|
||||
.. autosummary::
|
||||
|
||||
TextField
|
||||
TextFieldView
|
||||
|
||||
.. autoclass:: TextField
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: TextFieldView
|
||||
:members:
|
||||
18
help/en/_sources/developer/hscommon/gui/tree.rst.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
hscommon.gui.tree
|
||||
=================
|
||||
|
||||
.. automodule:: hscommon.gui.tree
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Tree
|
||||
Node
|
||||
|
||||
.. autoclass:: Tree
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: Node
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
16
help/en/_sources/developer/hscommon/index.rst.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
hscommon
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:glob:
|
||||
|
||||
build
|
||||
conflict
|
||||
desktop
|
||||
notify
|
||||
path
|
||||
util
|
||||
jobprogress/*
|
||||
gui/*
|
||||
|
||||
17
help/en/_sources/developer/hscommon/jobprogress/job.rst.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
hscommon.jobprogress.job
|
||||
========================
|
||||
|
||||
.. automodule:: hscommon.jobprogress.job
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Job
|
||||
NullJob
|
||||
|
||||
.. autoclass:: Job
|
||||
:members:
|
||||
:private-members:
|
||||
|
||||
.. autoclass:: NullJob
|
||||
:members:
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
hscommon.jobprogress.performer
|
||||
==============================
|
||||
|
||||
.. automodule:: hscommon.jobprogress.performer
|
||||
|
||||
.. autosummary::
|
||||
|
||||
ThreadedJobPerformer
|
||||
|
||||
.. autoclass:: ThreadedJobPerformer
|
||||
:members:
|
||||
|
||||
12
help/en/_sources/developer/hscommon/jobprogress/qt.rst.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
hscommon.jobprogress.qt
|
||||
=======================
|
||||
|
||||
.. automodule:: hscommon.jobprogress.qt
|
||||
|
||||
.. autosummary::
|
||||
|
||||
Progress
|
||||
|
||||
.. autoclass:: Progress
|
||||
:members:
|
||||
|
||||
5
help/en/_sources/developer/hscommon/notify.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
hscommon.notify
|
||||
===============
|
||||
|
||||
.. automodule:: hscommon.notify
|
||||
:members:
|
||||
5
help/en/_sources/developer/hscommon/path.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
hscommon.path
|
||||
=============
|
||||
|
||||
.. automodule:: hscommon.path
|
||||
:members:
|
||||
5
help/en/_sources/developer/hscommon/util.rst.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
hscommon.util
|
||||
=============
|
||||
|
||||
.. automodule:: hscommon.util
|
||||
:members:
|
||||
74
help/en/_sources/developer/index.rst.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
Developer Guide
|
||||
===============
|
||||
|
||||
When looking at a non-trivial codebase for the first time, it's very difficult to understand
|
||||
anything of it until you get the "Big Picture". This page is meant to, hopefully, make you get
|
||||
dupeGuru's big picture.
|
||||
|
||||
Branches and tags
|
||||
-----------------
|
||||
|
||||
The git repo has one main branch, ``master``. It represents the latest "stable development commit",
|
||||
that is, the latest commit that doesn't include in-progress features. This branch should always
|
||||
be buildable, ``tox`` should always run without errors on it.
|
||||
|
||||
When a feature/bugfix has an atomicity of a single commit, it's alright to commit right into
|
||||
``master``. However, if a feature/bugfix needs more than a commit, it should live in a separate
|
||||
topic branch until it's ready.
|
||||
|
||||
Every release is tagged with the version number. For example, there's a ``2.8.2`` tag for the
|
||||
v2.8.2 release.
|
||||
|
||||
Model/View/Controller... nope!
|
||||
------------------------------
|
||||
|
||||
dupeGuru's codebase has quite a few design flaws. The Model, View and Controller roles are filled by
|
||||
different classes, scattered around. If you're aware of that, it might help you to understand what
|
||||
the heck is going on.
|
||||
|
||||
The central piece of dupeGuru is :class:`core.app.DupeGuru`. It's the only
|
||||
interface to the python's code for the GUI code. A duplicate scan is started with
|
||||
:meth:`core.app.DupeGuru.start_scanning()`, directories are added through
|
||||
:meth:`core.app.DupeGuru.add_directory()`, etc..
|
||||
|
||||
A lot of functionalities of the App are implemented in the platform-specific subclasses of
|
||||
:class:`core.app.DupeGuru`, like ``DupeGuru`` in ``cocoa/inter/app.py``, or the ``DupeGuru`` class
|
||||
in ``qt/base/app.py``. For example, when performing "Remove Selected From Results",
|
||||
``RemoveSelected()`` on the cocoa side, and ``remove_duplicates()`` on the PyQt side, are
|
||||
respectively called to perform the thing.
|
||||
|
||||
.. _jobs:
|
||||
|
||||
Jobs
|
||||
----
|
||||
|
||||
A lot of operations in dupeGuru take a significant amount of time. This is why there's a generalized
|
||||
threaded job mechanism built-in :class:`~core.app.DupeGuru`. First, :class:`~core.app.DupeGuru` has
|
||||
a ``progress`` member which is an instance of
|
||||
:class:`~hscommon.jobprogress.performer.ThreadedJobPerformer`. It lets the GUI code know of the progress
|
||||
of the current threaded job. When :class:`~core.app.DupeGuru` needs to start a job, it calls
|
||||
``_start_job()`` and the platform specific subclass deals with the details of starting the job.
|
||||
|
||||
Core principles
|
||||
---------------
|
||||
|
||||
The core of the duplicate matching takes place (for SE and ME, not PE) in :mod:`core.engine`.
|
||||
There's :func:`core.engine.getmatches` which take a list of :class:`core.fs.File` instances and
|
||||
return a list of ``(firstfile, secondfile, match_percentage)`` matches. Then, there's
|
||||
:func:`core.engine.get_groups` which takes a list of matches and returns a list of
|
||||
:class:`.Group` instances (a :class:`.Group` is basically a list of :class:`.File` matching
|
||||
together).
|
||||
|
||||
When a scan is over, the final result (the list of groups from :func:`.get_groups`) is placed into
|
||||
:attr:`core.app.DupeGuru.results`, which is a :class:`core.results.Results` instance. The
|
||||
:class:`~.Results` instance is where all the dupe marking, sorting, removing, power marking, etc.
|
||||
takes place.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
core/index
|
||||
hscommon/index
|
||||
184
help/en/_sources/faq.rst.txt
Normal file
@@ -0,0 +1,184 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
.. contents::
|
||||
|
||||
What is dupeGuru?
|
||||
-----------------
|
||||
|
||||
dupeGuru is a tool to find duplicate files on your computer. It has three operational modes:
|
||||
Standard, Music and Picture. Each mode has its own specialized preferences.
|
||||
|
||||
Each mode has multiple scan types, such as filename, contents, tags. Some scan types feature
|
||||
advanced fuzzy matching algorithm, allowing you to find duplicates that other more rigid duplicate
|
||||
scanners can't.
|
||||
|
||||
What makes it special?
|
||||
----------------------
|
||||
|
||||
It's mostly about customizability. There's a lot of scanning options that allow you to get the
|
||||
type of results you're really looking for.
|
||||
|
||||
How safe is it to use dupeGuru?
|
||||
-------------------------------
|
||||
|
||||
Very safe. dupeGuru has been designed to make sure you don't delete files you didn't mean to delete.
|
||||
First, there is the reference folder system that lets you define folders where you absolutely
|
||||
**don't** want dupeGuru to let you delete files there, and then there is the group reference system
|
||||
that makes sure that you will **always** keep at least one member of the duplicate group.
|
||||
|
||||
How can I report a bug a suggest a feature?
|
||||
-------------------------------------------
|
||||
|
||||
dupeGuru is hosted on `Github`_ and it's also where issues are tracked. The best way to report a
|
||||
bug or suggest a feature is to sign up on Github and `open an issue`_.
|
||||
|
||||
The mark box of a file I want to delete is disabled. What must I do?
|
||||
--------------------------------------------------------------------
|
||||
|
||||
You cannot mark the reference (The first file) of a duplicate group. However, what you can do is to
|
||||
promote a duplicate file to reference. Thus, if a file you want to mark is reference, select a
|
||||
duplicate file in the group that you want to promote to reference, and click on
|
||||
**Actions-->Make Selected into Reference**. If the reference file is from a reference folder
|
||||
(filename written in blue letters), you cannot remove it from the reference position.
|
||||
|
||||
I have a folder from which I really don't want to delete files.
|
||||
---------------------------------------------------------------
|
||||
|
||||
If you want to be sure that dupeGuru will never delete file from a particular folder, make sure to
|
||||
set its state to **Reference** at :doc:`folders`.
|
||||
|
||||
What is this '(X discarded)' notice in the status bar?
|
||||
------------------------------------------------------
|
||||
|
||||
In some cases, some matches are not included in the final results for security reasons. Let me use
|
||||
an example. We have 3 file: A, B and C. We scan them using a low filter hardness. The scanner
|
||||
determines that A matches with B, A matches with C, but B does **not** match with C. Here, dupeGuru
|
||||
has kind of a problem. It cannot create a duplicate group with A, B and C in it because not all
|
||||
files in the group would match together. It could create 2 groups: one A-B group and then one A-C
|
||||
group, but it will not, for security reasons. Lets think about it: If B doesn't match with C, it
|
||||
probably means that either B, C or both are not actually duplicates. If there would be 2 groups (A-B
|
||||
and A-C), you would end up delete both B and C. And if one of them is not a duplicate, that is
|
||||
really not what you want to do, right? So what dupeGuru does in a case like this is to discard the
|
||||
A-C match (and adds a notice in the status bar). Thus, if you delete B and re-run a scan, you will
|
||||
have a A-C match in your next results.
|
||||
|
||||
I want to mark all files from a specific folder. What can I do?
|
||||
---------------------------------------------------------------
|
||||
|
||||
Enable the :doc:`Dupes Only <results>` mode and click on the Folder column to sort your duplicates
|
||||
by folder. It will then be easy for you to select all duplicates from the same folder, and then
|
||||
press Space to mark all selected duplicates.
|
||||
|
||||
I want to remove all files that are more than 300 KB away from their reference file. What can I do?
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
* Enable the :doc:`Dupes Only <results>` mode.
|
||||
* Enable the **Delta Values** mode.
|
||||
* Click on the "Size" column to sort the results by size.
|
||||
* Select all duplicates below -300.
|
||||
* Click on **Remove Selected from Results**.
|
||||
* Select all duplicates over 300.
|
||||
* Click on **Remove Selected from Results**.
|
||||
|
||||
I want to make my latest modified files reference files. What can I do?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
* Enable the :doc:`Dupes Only <results>` mode.
|
||||
* Enable the **Delta Values** mode.
|
||||
* Click on the "Modification" column to sort the results by modification date.
|
||||
* Click on the "Modification" column again to reverse the sort order.
|
||||
* Select all duplicates over 0.
|
||||
* Click on **Make Selected into Reference**.
|
||||
|
||||
I want to mark all duplicates containing the word "copy". How do I do that?
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
* Type "copy" in the "Filter" field in the top-right corner of the result window.
|
||||
* Click on **Mark --> Mark All**.
|
||||
|
||||
I want to remove all songs that are more than 3 seconds away from their reference file. What can I do?
|
||||
------------------------------------------------------------------------------------------------------
|
||||
|
||||
* Enable the :doc:`Dupes Only <results>` mode.
|
||||
* Enable the **Delta Values** mode.
|
||||
* Click on the "Time" column to sort the results by time.
|
||||
* Select all duplicates below -00:03.
|
||||
* Click on **Remove Selected from Results**.
|
||||
* Select all duplicates over 00:03.
|
||||
* Click on **Remove Selected from Results**.
|
||||
|
||||
I want to make my highest bitrate songs reference files. What can I do?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
* Enable the :doc:`Dupes Only <results>` mode.
|
||||
* Enable the **Delta Values** mode.
|
||||
* Click on the "Bitrate" column to sort the results by bitrate.
|
||||
* Click on the "Bitrate" column again to reverse the sort order.
|
||||
* Select all duplicates over 0.
|
||||
* Click on **Make Selected into Reference**.
|
||||
|
||||
I don't want [live] and [remix] versions of my songs counted as duplicates. How do I do that?
|
||||
---------------------------------------------------------------------------------------------
|
||||
|
||||
If your comparison threshold is low enough, you will probably end up with live and remix
|
||||
versions of your songs in your results. There's nothing you can do to prevent that, but there's
|
||||
something you can do to easily remove them from your results after the scan: post-scan
|
||||
filtering. If, for example, you want to remove every song with anything inside square brackets
|
||||
[]:
|
||||
|
||||
* Type "[*]" in the "Filter" field in the top-right corner of the result window.
|
||||
* Click on **Mark --> Mark All**.
|
||||
* Click on **Actions --> Remove Selected from Results**.
|
||||
|
||||
The "Filter Hardness" slider in the preferences won't move!
|
||||
-----------------------------------------------------------
|
||||
|
||||
This slider is only relevant for scan types that support "fuzziness". Many scan types, such as the
|
||||
"Contents" type, only support exact matches. When these types are selected, the slider is disabled.
|
||||
|
||||
On some OS, the fact that it's disabled is harder to see than on others, but if you can't move the
|
||||
slider, it means that this preference is irrelevant in your current scan type.
|
||||
|
||||
I've tried to send my duplicates to Trash, but dupeGuru is telling me it can't do it. Why? What can I do?
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Most of the time, the reason why dupeGuru can't send files to Trash is because of file permissions.
|
||||
You need *write* permissions on files you want to send to Trash.
|
||||
|
||||
If dupeGuru still gives you troubles after fixing your permissions, try enabling the "Directly
|
||||
delete files" option that is offered to you when you activate Send to Trash. This will not send
|
||||
files to the Trash, but delete them immediately. In some cases, for example on network storage
|
||||
(NAS), this has been known to work when normal deletion didn't.
|
||||
|
||||
If this fail, `HS forums`_ might be of some help.
|
||||
|
||||
Why is Picture mode's contents scan so slow?
|
||||
--------------------------------------------
|
||||
|
||||
This scanning method is very different from methods. It can detect duplicate photos even if they
|
||||
are not exactly the same. This very cool capability has a cost: time. Every picture has to be
|
||||
individually and fuzzily matched to all others, and this takes a lot of CPU power.
|
||||
|
||||
If all you need to find is exact duplicates, just use the standard mode of dupeGuru with the
|
||||
Contents scan method. If your photos have EXIF tags, you can also try the "EXIF" scan method which
|
||||
is much faster.
|
||||
|
||||
Where are user files located?
|
||||
-----------------------------
|
||||
|
||||
For some reason, you'd like to remove or edit dupeGuru's user files (debug logs, caches, etc.).
|
||||
Where they're located depends on your platform:
|
||||
|
||||
* Linux: ``~/.local/share/data/Hardcoded Software/dupeGuru``
|
||||
* Mac OS X: ``~/Library/Application Support/dupeGuru``
|
||||
|
||||
Preferences are stored elsewhere:
|
||||
|
||||
* Linux: ``~/.config/Hardcoded Software/dupeGuru.conf``
|
||||
* Mac OS X: In the built-in ``defaults`` system, as ``com.hardcoded-software.dupeguru``
|
||||
|
||||
.. _HS forums: https://forum.hardcoded.net/
|
||||
.. _Github: https://github.com/hsoft/dupeguru
|
||||
.. _open an issue: https://github.com/hsoft/dupeguru/wiki/issue-labels
|
||||
|
||||
76
help/en/_sources/folders.rst.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
Folder Selection
|
||||
================
|
||||
|
||||
The first window you see when you launch dupeGuru is the folder selection window. This windows
|
||||
contains the basic input dupeGuru needs to start a scan:
|
||||
|
||||
* An Application Mode selection
|
||||
* A Scan Type selection
|
||||
* Folders to scan
|
||||
|
||||
Application Mode
|
||||
----------------
|
||||
|
||||
dupeGuru had three main modes: Standard, Music and Picture.
|
||||
|
||||
Standard is for any type of files. This makes this mode the most polyvalent, but it lacks
|
||||
specialized features other modes have.
|
||||
|
||||
Music mode scans only music files, but it supports tags comparison and its results window has many
|
||||
audio-related informational columns.
|
||||
|
||||
Picture mode scans only pictures, but its contents scan type is a powerful fuzzy matcher that can
|
||||
find pictures that are similar without being exactly the same.
|
||||
|
||||
Choosing an application mode not only changes available scan types in the selector below, but also
|
||||
changes available options in the preferences panel. Thus, if you want to fine tune your scan, be
|
||||
sure to open the preferences panel **after** you've selected the application mode.
|
||||
|
||||
Scan Type
|
||||
---------
|
||||
|
||||
This selector determines the type of the scan we'll do. See :doc:`scan` for details about scan
|
||||
types.
|
||||
|
||||
Folder List
|
||||
-----------
|
||||
|
||||
To add a folder, click on the **+** button. If you added folder before, a popup
|
||||
menu with a list of recent folders you added will pop. You can click on one of
|
||||
them to add it directly to your list. If you click on the first item of the
|
||||
popup menu, **Add New Folder...**, you will be prompted for a folder to add. If
|
||||
you never added a folder, no menu will pop and you will directly be prompted
|
||||
for a new folder to add.
|
||||
|
||||
An alternate way to add folders to the list is to drag them in the list.
|
||||
|
||||
To remove a folder, select the folder to remove and click on **-**. If a subfolder is selected when
|
||||
you click the button, the selected folder will be set to **excluded** state (see below) instead of
|
||||
being removed.
|
||||
|
||||
Folder states
|
||||
-------------
|
||||
|
||||
Every folder can be in one of these 3 states:
|
||||
|
||||
**Normal:**
|
||||
Duplicates found in this folder can be deleted.
|
||||
**Reference:**
|
||||
Duplicates found in this folder **cannot** be deleted. Files from this folder can
|
||||
only end up in **reference** position in the dupe group. If more than one file from reference
|
||||
folders end up in the same dupe group, only one will be kept. The others will be removed from
|
||||
the group.
|
||||
**Excluded:**
|
||||
Files in this directory will not be included in the scan.
|
||||
|
||||
The default state of a folder is, of course, **Normal**. You can use **Reference** state for a
|
||||
folder if you want to be sure that you won't delete any file from it.
|
||||
|
||||
When you set the state of a directory, all subfolders of this folder automatically inherit this
|
||||
state unless you explicitly set a subfolder's state.
|
||||
|
||||
Scan
|
||||
----
|
||||
|
||||
When you're ready, click on the **Scan** button to initiate the scanning process. When it's done,
|
||||
you'll be shown the :doc:`results`.
|
||||
45
help/en/_sources/index.rst.txt
Normal file
@@ -0,0 +1,45 @@
|
||||
dupeGuru help
|
||||
=============
|
||||
|
||||
This help document is also available in these languages:
|
||||
|
||||
* `French <http://www.hardcoded.net/dupeguru/help/fr>`__
|
||||
* `German <http://www.hardcoded.net/dupeguru/help/de>`__
|
||||
* `Armenian <http://www.hardcoded.net/dupeguru/help/hy>`__
|
||||
* `Russian <http://www.hardcoded.net/dupeguru/help/ru>`__
|
||||
* `Ukrainian <http://www.hardcoded.net/dupeguru/help/uk>`__
|
||||
|
||||
dupeGuru is a tool to find duplicate files on your computer. It has three
|
||||
modes, Standard, Music and Picture, with each mode having its own scan types
|
||||
and little features.
|
||||
|
||||
Although dupeGuru can easily be used without documentation, reading this file
|
||||
will help you to master it. If you are looking for guidance for your first
|
||||
duplicate scan, you can take a look at the :doc:`Quick Start <quick_start>`
|
||||
section.
|
||||
|
||||
It is a good idea to keep dupeGuru updated. You can download the latest version on its `homepage`_.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
contribute
|
||||
quick_start
|
||||
folders
|
||||
preferences
|
||||
scan
|
||||
results
|
||||
reprioritize
|
||||
faq
|
||||
developer/index
|
||||
changelog
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _homepage: https://www.hardcoded.net/dupeguru
|
||||
78
help/en/_sources/preferences.rst.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
Preferences
|
||||
===========
|
||||
|
||||
**Tags to scan:**
|
||||
When using the **Tags** scan type, you can select the tags that will be used for comparison.
|
||||
|
||||
**Word weighting:**
|
||||
See :ref:`word-weighting`.
|
||||
|
||||
**Match similar words:**
|
||||
See :ref:`similarity-matching`.
|
||||
|
||||
**Match pictures of different dimensions:**
|
||||
If you check this box, pictures of different dimensions will be allowed in the same
|
||||
duplicate group.
|
||||
|
||||
.. _filter-hardness:
|
||||
|
||||
**Filter Hardness:**
|
||||
The threshold needed for two files to be considered duplicates. A lower value means more
|
||||
duplicates. The meaning of the threshold depends on the scanning type (see :doc:`scan`).
|
||||
Only works for :ref:`worded <worded-scan>` and :ref:`picture blocks <picture-blocks-scan>`
|
||||
scans.
|
||||
|
||||
**Can mix file kind:**
|
||||
If you check this box, duplicate groups are allowed to have files with different extensions. If
|
||||
you don't check it, well, they aren't!
|
||||
|
||||
**Ignore duplicates hardlinking to the same file:**
|
||||
If this option is enabled, dupeGuru will verify duplicates to see if they refer to the same
|
||||
`inode`_. If they do, they will not be considered duplicates. (Only for OS X and Linux)
|
||||
|
||||
**Use regular expressions when filtering:**
|
||||
If you check this box, the filtering feature will treat your filter query as a
|
||||
**regular expression**. Explaining them is beyond the scope of this document. A good place to
|
||||
start learning it is `regular-expressions.info`_.
|
||||
|
||||
**Remove empty folders after delete or move:**
|
||||
When this option is enabled, folders are deleted after a file is deleted or moved and the folder
|
||||
is empty.
|
||||
|
||||
**Copy and Move:**
|
||||
Determines how the Copy and Move operations (in the Action menu) will behave.
|
||||
|
||||
* **Right in destination:** All files will be sent directly in the selected destination, without
|
||||
trying to recreate the source path at all.
|
||||
* **Recreate relative path:** The source file's path will be re-created in the destination folder up
|
||||
to the root selection in the Directories panel. For example, if you added
|
||||
``/Users/foobar/SomeFolder`` to your Directories panel and you move
|
||||
``/Users/foobar/SomeFolder/SubFolder/SomeFile.ext`` to the destination
|
||||
``/Users/foobar/MyDestination``, the final destination for the file will be
|
||||
``/Users/foobar/MyDestination/SubFolder`` (``SomeFolder`` has been trimmed from source's path in
|
||||
the final destination.).
|
||||
* **Recreate absolute path:** The source file's path will be re-created in the destination folder in
|
||||
its entirety. For example, if you move ``/Users/foobar/SomeFolder/SubFolder/SomeFile.ext`` to the
|
||||
destination ``/Users/foobar/MyDestination``, the final destination for the file will be
|
||||
``/Users/foobar/MyDestination/Users/foobar/SomeFolder/SubFolder``.
|
||||
|
||||
In all cases, dupeGuru nicely handles naming conflicts by prepending a number to the destination
|
||||
filename if the filename already exists in the destination.
|
||||
|
||||
**Custom Command:**
|
||||
This preference determines the command that will be invoked by the "Invoke Custom Command"
|
||||
action. You can invoke any external application through this action. This can be useful if,
|
||||
for example, you have a nice diffing application installed.
|
||||
|
||||
The format of the command is the same as what you would write in the command line, except that there
|
||||
are 2 placeholders: **%d** and **%r**. These placeholders will be replaced by the path of the
|
||||
selected dupe (%d) and the path of the selected dupe's reference file (%r).
|
||||
|
||||
If the path to your executable contains space characters, you should enclose it in "" quotes. You
|
||||
should also enclose placeholders in quotes because it's very possible that paths to dupes and refs
|
||||
will contain spaces. Here's an example custom command::
|
||||
|
||||
"C:\Program Files\SuperDiffProg\SuperDiffProg.exe" "%d" "%r"
|
||||
|
||||
.. _inode: http://en.wikipedia.org/wiki/Inode
|
||||
.. _regular-expressions.info: http://www.regular-expressions.info
|
||||
14
help/en/_sources/quick_start.rst.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
Quick Start
|
||||
===========
|
||||
|
||||
To get you quickly started with dupeGuru, let's just make a standard scan using default preferences.
|
||||
|
||||
* Launch dupeGuru.
|
||||
* Add folders to scan with either drag & drop or the "+" button.
|
||||
* Click on **Scan**.
|
||||
* Wait until the scan process is over.
|
||||
* Look at every duplicate (The files that are indented) and verify that it is indeed a duplicate to the group's reference (The file above the duplicate that is not indented and have a disabled mark box).
|
||||
* If a file is a false duplicate, select it and click on **Actions-->Remove Selected from Results**.
|
||||
* Once you are sure that there is no false duplicate in your results, click on **Edit-->Mark All**, and then **Actions-->Send Marked to Recycle bin**.
|
||||
|
||||
That is only a basic scan. There are a lot of tweaking you can do to get different results and several methods of examining and modifying your results. To know about them, just read the rest of this help file.
|
||||
25
help/en/_sources/reprioritize.rst.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
Re-Prioritizing duplicates
|
||||
==========================
|
||||
|
||||
dupeGuru tries to automatically determine which duplicate should go in each group's reference
|
||||
position, but sometimes it gets it wrong. In many cases, clever dupe sorting with "Delta Values"
|
||||
and "Dupes Only" options in addition to the "Make Selected into Reference" action does the trick,
|
||||
but sometimes, a more powerful option is needed. This is where the Re-Prioritization dialog comes
|
||||
into play. You can summon it through the "Re-Prioritize Results" item in the "Actions" menu.
|
||||
|
||||
This dialog allows you to select criteria according to which a reference dupe will be selected in
|
||||
each dupe group. The list of available criteria is on the left and the list of criteria you've
|
||||
selected is on the right.
|
||||
|
||||
A criteria is a category followed by an argument. For example, "Size (Highest)" means that the dupe
|
||||
with the biggest size will win. "Folder (/foo/bar)" means that dupes in this folder will win. To add
|
||||
a criterion to the rightmost list, first select a category in the combobox, then select a
|
||||
subargument in the list below, and then click on the right pointing arrow button.
|
||||
|
||||
The order of the list on the right is important (you can re-order items through drag & drop). When
|
||||
picking a dupe for reference position, the first criterion is used. If there's a tie, the second
|
||||
criterion is used and so on and so on. For example, if your arguments are "Size (Highest)" and then
|
||||
"Filename (Doesn't end with a number)", the reference file that will be picked in a group will be
|
||||
the biggest file, and if two or more files have the same size, the one that has a filename that
|
||||
doesn't end with a number will be used. When all criteria result in ties, the order in which dupes
|
||||
previously were in the group will be used.
|
||||
196
help/en/_sources/results.rst.txt
Normal file
@@ -0,0 +1,196 @@
|
||||
Results
|
||||
=======
|
||||
|
||||
.. contents::
|
||||
|
||||
When dupeGuru is finished scanning for duplicates, it will show its results in the form of duplicate group list.
|
||||
|
||||
About duplicate groups
|
||||
----------------------
|
||||
|
||||
A duplicate group is a group of files that all match together. Every group has a **reference file** and one or more **duplicate files**. The reference file is the first file of the group. Its mark box is disabled. Below it, and indented, are the duplicate files.
|
||||
|
||||
You can mark duplicate files, but you can never mark the reference file of a group. This is a security measure to prevent dupeGuru from deleting not only duplicate files, but their reference. You sure don't want that, do you?
|
||||
|
||||
What determines which files are reference and which files are duplicates is first their folder state. A file from a reference folder will always be reference in a duplicate group. If all files are from a normal folder, the size determine which file will be the reference of a duplicate group. dupeGuru assumes that you always want to keep the biggest file, so the biggest files will take the reference position.
|
||||
|
||||
You can change the reference file of a group manually. To do so, select the duplicate file you want
|
||||
to promote to reference, and click on **Actions-->Make Selected into Reference**.
|
||||
|
||||
Reviewing results
|
||||
-----------------
|
||||
|
||||
Although you can just click on **Edit-->Mark All** and then **Actions-->Send Marked to Recycle bin** to quickly delete all duplicate files in your results, it is always recommended to review all duplicates before deleting them.
|
||||
|
||||
To help you reviewing the results, you can bring up the **Details panel**. This panel shows all the details of the currently selected file as well as its reference's details. This is very handy to quickly determine if a duplicate really is a duplicate. You can also double-click on a file to open it with its associated application.
|
||||
|
||||
If you have more false duplicates than true duplicates (If your filter hardness is very low), the best way to proceed would be to review duplicates, mark true duplicates and then click on **Actions-->Send Marked to Recycle bin**. If you have more true duplicates than false duplicates, you can instead mark all files that are false duplicates, and use **Actions-->Remove Marked from Results**.
|
||||
|
||||
Marking and Selecting
|
||||
---------------------
|
||||
|
||||
A **marked** duplicate is a duplicate with the little box next to it having a check-mark. A **selected** duplicate is a duplicate being highlighted. The multiple selection actions can be performed in dupeGuru in the standard way (Shift/Command/Control click). You can toggle all selected duplicates' mark state by pressing **space**.
|
||||
|
||||
Show Dupes Only
|
||||
---------------
|
||||
|
||||
When this mode is enabled, the duplicates are shown without their respective reference file. You can select, mark and sort this list, just like in normal mode.
|
||||
|
||||
The dupeGuru results, when in normal mode, are sorted according to duplicate groups' **reference file**. This means that if you want, for example, to mark all duplicates with the "exe" extension, you cannot just sort the results by "Kind" to have all exe duplicates together because a group can be composed of more than one kind of files. That is where Dupes Only mode comes into play. To mark all your "exe" duplicates, you just have to:
|
||||
|
||||
* Enable the Dupes Only mode.
|
||||
* Add the "Kind" column with the "Columns" menu.
|
||||
* Click on that "Kind" column to sort the list by kind.
|
||||
* Locate the first duplicate with a "exe" kind.
|
||||
* Select it.
|
||||
* Scroll down the list to locate the last duplicate with a "exe" kind.
|
||||
* Hold Shift and click on it.
|
||||
* Press Space to mark all selected duplicates.
|
||||
|
||||
.. _deltavalues:
|
||||
|
||||
Delta Values
|
||||
------------
|
||||
|
||||
If you turn this switch on, numerical columns will display the value relative to the duplicate's
|
||||
reference instead of the absolute values. These delta values will also be displayed in a different
|
||||
color, orange, so you can spot them easily. For example, if a duplicate is 1.2 MB and its reference
|
||||
is 1.4 MB, the Size column will display -0.2 MB.
|
||||
|
||||
Moreover, non-numerical values will also be in orange if their value is different from their
|
||||
reference, and stay black if their value is the same. Combined with column sorting in Dupes Only
|
||||
mode, this allows for very powerful post-scan filtering.
|
||||
|
||||
Dupes Only and Delta Values
|
||||
---------------------------
|
||||
|
||||
The Dupes Only mode unveil its true power when you use it with the Delta Values switch turned on.
|
||||
When you turn it on, relative values will be displayed instead of absolute ones. So if, for example,
|
||||
you want to remove from your results all duplicates that are more than 300 KB away from their
|
||||
reference, you could sort the dupes only results by Size, select all duplicates under -300 in the
|
||||
Size column, delete them, and then do the same for duplicates over 300 at the bottom of the list.
|
||||
|
||||
Same thing for non-numerical values: When Dupes Only and Delta Values are enabled at the same time,
|
||||
column sorting groups rows depending on whether they're orange or not. Example: You ran a contents
|
||||
scan, but you would only like to delete duplicates that have the same filename? Sort by filename
|
||||
and all dupes with their filename attribute being the same as the reference will be grouped
|
||||
together, their value being in black.
|
||||
|
||||
You could also use it to change the reference priority of your duplicate list. When you make a fresh
|
||||
scan, if there are no reference folders, the reference file of every group is the biggest file. If
|
||||
you want to change that, for example, to the latest modification time, you can sort the dupes only
|
||||
results by modification time in **descending** order, select all duplicates with a modification time
|
||||
delta value higher than 0 and click on **Make Selected into Reference**. The reason why you must
|
||||
make the sort order descending is because if 2 files among the same duplicate group are selected
|
||||
when you click on **Make Selected into Reference**, only the first of the list will be made
|
||||
reference, the other will be ignored. And since you want the last modified file to be reference,
|
||||
having the sort order descending assures you that the first item of the list will be the last
|
||||
modified.
|
||||
|
||||
Filtering
|
||||
---------
|
||||
|
||||
dupeGuru supports post-scan filtering. With it, you can narrow down your results so you can perform
|
||||
actions on a subset of it. For example, you could easily mark all duplicates with their filename
|
||||
containing "copy" from your results using the filter.
|
||||
|
||||
To use the filtering feature, type your filter in the "Filter" search field at the top-right corner
|
||||
of the results window. What you type in that box will be applied to the *whole path* of every
|
||||
duplicate in the results. Only duplicate *groups* having at least one duplicate matching the filter
|
||||
will be shown.
|
||||
|
||||
When having groups where not all duplicates match the filter, we still show all duplicates of
|
||||
the group. However, non-matching duplicates are in "reference mode". Therefore, you can perform
|
||||
actions like "Mark All" and be sure to only mark filtered duplicates.
|
||||
|
||||
To go back to unfiltered result, blank out the field or click on the "X".
|
||||
|
||||
In simple mode (the default mode), whatever you type as the filter is the string used to perform the
|
||||
actual filtering, with the exception of one wildcard: **\***. Thus, if you type "[*]" as your
|
||||
filter, it will match anything with [] brackets in it, whatever is in between those brackets.
|
||||
|
||||
For more advanced filtering, you can turn "Use regular expressions when filtering" on. The filtering
|
||||
feature will then use **regular expressions**. A regular expression is a language for matching text.
|
||||
Explaining them is beyond the scope of this document. A good place to start learning it is
|
||||
`regular-expressions.info`_.
|
||||
|
||||
Matches are case insensitive in both simple and regexp mode.
|
||||
|
||||
For the filter to match, your regular expression don't have to match the whole filename, it just
|
||||
have to contain a string matching the expression.
|
||||
|
||||
Action Menu
|
||||
-----------
|
||||
|
||||
**Clear Ignore List:**
|
||||
Remove all ignored matches you added. You have to start a new scan for the
|
||||
newly cleared ignore list to be effective.
|
||||
**Export Results to XHTML:**
|
||||
Take the current results, and create an XHTML file out of it. The
|
||||
columns that are visible when you click on this button will be the columns present in the XHTML
|
||||
file. The file will automatically be opened in your default browser.
|
||||
**Send Marked to Trash:**
|
||||
Send all marked duplicates to trash, obviously. Before proceeding,
|
||||
you'll be presented deletion options (see below).
|
||||
**Move Marked to...:**
|
||||
Prompt you for a destination, and then move all marked files to that
|
||||
destination. Source file's path might be re-created in destination, depending on the
|
||||
"Copy and Move" preference.
|
||||
**Copy Marked to...:**
|
||||
Prompt you for a destination, and then copy all marked files to that
|
||||
destination. Source file's path might be re-created in destination, depending on the
|
||||
"Copy and Move" preference.
|
||||
**Remove Marked from Results:**
|
||||
Remove all marked duplicates from results. The actual files will
|
||||
not be touched and will stay where they are.
|
||||
**Remove Selected from Results:**
|
||||
Remove all selected duplicates from results. Note that all
|
||||
selected reference files will be ignored, only duplicates can be removed with this action.
|
||||
**Make Selected into Reference:**
|
||||
Promote all selected duplicates to reference. If a duplicate is
|
||||
a part of a group having a reference file coming from a reference folder (in blue color), no
|
||||
action will be taken for this duplicate. If more than one duplicate among the same group are
|
||||
selected, only the first of each group will be promoted.
|
||||
**Add Selected to Ignore List:**
|
||||
This first removes all selected duplicates from results, and
|
||||
then add the match of that duplicate and the current reference in the ignore list. This match
|
||||
will not come up again in further scan. The duplicate itself might come back, but it will be
|
||||
matched with another reference file. You can clear the ignore list with the Clear Ignore List
|
||||
command.
|
||||
**Open Selected with Default Application:**
|
||||
Open the file with the application associated with selected file's type.
|
||||
**Reveal Selected in Finder:**
|
||||
Open the folder containing selected file.
|
||||
**Invoke Custom Command:**
|
||||
Invokes the external application you've set up in your preferences using the current selection
|
||||
as arguments in the invocation.
|
||||
**Rename Selected:**
|
||||
Prompts you for a new name, and then rename the selected file.
|
||||
|
||||
Deletion Options
|
||||
----------------
|
||||
|
||||
These options affect how duplicate deletion takes place. Most of the time, you don't need to enable
|
||||
any of them.
|
||||
|
||||
**Link deleted files:**
|
||||
The deleted files are replaced by a link to the reference file. You have a choice of replacing
|
||||
it either with a `symlink`_ or a `hardlink`_. It's better to read the whole
|
||||
wikipedia pages about them to make a informed choice, but in short, a symlink is a shortcut to
|
||||
the file's path. If the original file is deleted or moved, the link is broken. A hardlink is a
|
||||
link to the file *itself*. That link is as good as a "real" file. Only when *all* hardlinks to a
|
||||
file are deleted is the file itself deleted.
|
||||
|
||||
On OSX and Linux, this feature is supported fully, but under Windows, it's a bit complicated.
|
||||
Windows XP doesn't support it, but Vista and up support it. However, for the feature to work,
|
||||
dupeGuru has to run with administrative privileges.
|
||||
|
||||
**Directly delete files:**
|
||||
Instead of sending files to trash, directly delete them. This is used
|
||||
for troubleshooting and you normally don't need to enable this unless dupeGuru has problems
|
||||
deleting files normally, something that can happens when you try to delete files on network
|
||||
storage (NAS).
|
||||
|
||||
.. _regular-expressions.info: http://www.regular-expressions.info
|
||||
.. _hardlink: http://en.wikipedia.org/wiki/Hard_link
|
||||
.. _symlink: http://en.wikipedia.org/wiki/Symbolic_link
|
||||
168
help/en/_sources/scan.rst.txt
Normal file
@@ -0,0 +1,168 @@
|
||||
The scanning process
|
||||
====================
|
||||
|
||||
.. contents::
|
||||
|
||||
dupeGuru has 3 basic ways of scanning: :ref:`worded-scan` and :ref:`contents-scan` and
|
||||
:ref:`picture blocks <picture-blocks-scan>`. The first two types are for the Standard and Music
|
||||
modes, the last is for the Picture mode. The scanning process is configured through the
|
||||
:doc:`Preference pane <preferences>`.
|
||||
|
||||
.. _worded-scan:
|
||||
|
||||
Worded scans
|
||||
------------
|
||||
|
||||
Worded scans extract a string from each file and split it into words. The string can come from two
|
||||
different sources: **Filename** or **Tags** (Music Edition only).
|
||||
|
||||
When our source is music tags, we have to choose which tags to use. If, for example, we choose to
|
||||
analyse *artist* and *title* tags, we'd end up with strings like
|
||||
"The White Stripes - Seven Nation Army".
|
||||
|
||||
Words are split by space characters, with all punctuation removed (some are replaced by spaces, some
|
||||
by nothing) and all words lowercased. For example, the string "This guy's song(remix)" yields
|
||||
*this*, *guys*, *song* and *remix*.
|
||||
|
||||
Once this is done, the scanning dance begins. Finding duplicates is only a matter of finding how
|
||||
many words in common two given strings have. If the :ref:`filter hardness <filter-hardness>` is,
|
||||
for example, ``80``, it means that 80% of the words of two strings must match. To determine the
|
||||
matching percentage, dupeGuru first counts the total number of words in **both** strings, then count
|
||||
the number of words matching (every word matching count as 2), and then divide the number of words
|
||||
matching by the total number of words. If the result is higher or equal than the filter hardness,
|
||||
we have a duplicate match. For example, "a b c d" and "c d e" have a matching percentage of 57
|
||||
(4 words matching, 7 total words).
|
||||
|
||||
Fields
|
||||
^^^^^^
|
||||
|
||||
Song filenames often come with multiple and distinct parts and this can cause problems. For example,
|
||||
let's take these two songs: "Dolly Parton - I Will Always Love You" and
|
||||
"Whitney Houston - I Will Always Love You". They are clearly not the same song (they come from
|
||||
different artists), but they still still have a matching score of 71%! This means that, with a naive
|
||||
scanning method, we would get these songs as a false positive as soon as we try to dig a bit deeper
|
||||
in our dupe hunt by lowering the threshold a bit.
|
||||
|
||||
This is why we have the "Fields" concept. Fields are separated by dashes (``-``). When the
|
||||
"Filename - Fields" scan type is chosen, each field is compared separately. Our final matching score
|
||||
will only be the lowest of all the fields. In our example, the title has a 100% match, but the
|
||||
artist has a 0% match, making our final match score 0.
|
||||
|
||||
Sometimes, our song filename policy isn't completely homogenous, which means that we can end up with
|
||||
"The White Stripes - Seven Nation Army" and "Seven Nation Army - The White Stripes". This is why
|
||||
we have the "Filename - Fields (No Order)" scan type. With this scan type, all fields are compared
|
||||
with each other, and the highest score is kept. Then, the final matching score is the lowest of them
|
||||
all. In our case, the final matching score is 100.
|
||||
|
||||
Note: Each field is used once. Thus, "The White Stripes - The White Stripes" and
|
||||
"The White Stripes - Seven Nation Army" have a match score of 0 because the second
|
||||
"The White Stripes" can't be compared with the first field of the other name because it has already
|
||||
been "used up" by the first field. Our final match score would be 0.
|
||||
|
||||
*Tags* scanning method is always "fielded". When choosing this scan method, we also choose which
|
||||
tags are going to be compared, each being a field.
|
||||
|
||||
.. _word-weighting:
|
||||
|
||||
Word weighting
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
When enabled, this option slightly changes how matching percentage is calculated by making bigger
|
||||
words worth more. With word weighting, instead of having a value of 1 in the duplicate count and
|
||||
total word count, every word have a value equal to the number of characters they have. With word
|
||||
weighting, "ab cde fghi" and "ab cde fghij" would have a matching percentage of 53% (19 total
|
||||
characters, 10 characters matching (4 for "ab" and 6 for "cde")).
|
||||
|
||||
.. _similarity-matching:
|
||||
|
||||
Similarity matching
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When enabled, similar words will be counted as matches. For example "The White Stripes" and
|
||||
"The White Stripe" would have a match score of 100 instead of 66 with that option turned on.
|
||||
|
||||
Two words are considered similar if they can be made equal with only a few edit operations (removing
|
||||
a letter, adding one etc.). The process used is not unlike the
|
||||
`Levenshtein distance`_. For the technically inclined, the actual function used is
|
||||
Python's `get_close_matches`_ with a ``0.8`` cutoff.
|
||||
|
||||
**Warning:** Use this option with caution. It is likely that you will get a lot of false positives
|
||||
in your results when turning it on. However, it will help you to find duplicates that you wouldn't
|
||||
have found otherwise. The scan process also is significantly slower with this option turned on.
|
||||
|
||||
.. _contents-scan:
|
||||
|
||||
Contents scans
|
||||
--------------
|
||||
|
||||
Contents scans are much simpler than worded scans. We read files and if the contents is exactly the
|
||||
same, we consider the two files duplicates.
|
||||
|
||||
This is, of course, quite longer than comparing filenames and, to avoid needlessly reading whole
|
||||
file contents, we start by looking at file sizes. After having grouped our files by size, we discard
|
||||
every file that is alone in its group. Then, we proceed to read the contents of our remaining files.
|
||||
|
||||
MD5 hashes are used to compute compare contents. Yes, it is widely known that forging files having
|
||||
the same MD5 hash is easy, but this file has to be knowingly forged. The possibilities of two files
|
||||
having the same MD5 hash *and* the same size by accident is still very, very small.
|
||||
|
||||
The :ref:`filter hardness <filter-hardness>` preference is ignored in this scan.
|
||||
|
||||
Folders
|
||||
^^^^^^^
|
||||
|
||||
This is a special Contents scan type. It works like a normal contents scan, but
|
||||
instead of trying to find duplicate files, it tries to find duplicate folders.
|
||||
A folder is duplicate to another if all files it contains have the same
|
||||
contents as the other folder's file.
|
||||
|
||||
This scan is, of course, recursive and subfolders are checked. dupeGuru keeps only the biggest
|
||||
fishes. Therefore, if two folders that are considered as matching contain subfolders, these
|
||||
subfolders will not be included in the final results.
|
||||
|
||||
With this mode, we end up with folders as results instead of files.
|
||||
|
||||
.. _picture-blocks-scan:
|
||||
|
||||
Picture blocks
|
||||
--------------
|
||||
|
||||
dupeGuru Picture mode stands apart of its two friends. Its scan types are completely different.
|
||||
The first one is its "Contents" scan, which is a bit too generic, hence the name we use here,
|
||||
"Picture blocks".
|
||||
|
||||
We start by opening every picture in RGB bitmap mode, then we "blockify" the picture. We create a
|
||||
15x15 grid and compute the average color of each grid tile. This is the "picture analysis" phase.
|
||||
It's very time consuming and the result is cached in a database (the "picture cache").
|
||||
|
||||
Once we've done that, we can start comparing them. Each tile in the grid (an average color) is
|
||||
compared to its corresponding grid on the other picture and a color diff is computer (it's simply
|
||||
a sum of the difference of R, G and B on each side). All these sums are added up to a final "score".
|
||||
|
||||
If that score is smaller or equal to ``100 - threshold``, we have a match.
|
||||
|
||||
A threshold of 100 adds an additional constraint that pictures have to be exactly the same (it's
|
||||
possible, due to averaging, that the tile comparison yields ``0`` for pictures that aren't exactly
|
||||
the same, but since "100%" suggests "exactly the same", we discard those ocurrences). If you want
|
||||
to get pictures that are very, very similar but still allow a bit of fuzzy differences, go for 99%.
|
||||
|
||||
This second part of the scan is CPU intensive and can take quite a bit of time. This task has been
|
||||
made to take advatange of multi-core CPUs and has been optimized to the best of my abilities, but
|
||||
the fact of the matter is that, due to the fuzziness of the task, we still have to compare every picture
|
||||
to every other, making the algorithm quadratic (if ``N`` is the number of pictures to compare, the
|
||||
number of comparisons to perform is ``N*N``).
|
||||
|
||||
This algorithm is very naive, but in the field, it works rather well. If you master a better
|
||||
algorithm and want to improve dupeGuru, by all means, let me know!
|
||||
|
||||
EXIF Timestamp
|
||||
--------------
|
||||
|
||||
This one is easy. We read the EXIF information of every picture and extract the ``DateTimeOriginal``
|
||||
tag. If the tag is the same for two pictures, they're considered duplicates.
|
||||
|
||||
**Warning:** Modified pictures often keep the same EXIF timestamp, so watch out for false positives
|
||||
when you use that scan type.
|
||||
|
||||
.. _Levenshtein distance: http://en.wikipedia.org/wiki/Levenshtein_distance
|
||||
.. _get_close_matches: http://docs.python.org/3/library/difflib.html#difflib.get_close_matches
|
||||
BIN
help/en/_static/ajax-loader.gif
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
help/en/_static/alert_info_32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
help/en/_static/alert_warning_32.png
Normal file
|
After Width: | Height: | Size: 944 B |
665
help/en/_static/basic.css
Normal file
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
* basic.css
|
||||
* ~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- main layout ----------------------------------------------------------- */
|
||||
|
||||
div.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 10px 5px 0 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
float: left;
|
||||
width: 230px;
|
||||
margin-left: -100%;
|
||||
font-size: 90%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap : break-word;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul,
|
||||
div.sphinxsidebar ul.want-points {
|
||||
margin-left: 20px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="text"] {
|
||||
float: left;
|
||||
width: 80%;
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="submit"] {
|
||||
float: left;
|
||||
width: 20%;
|
||||
border-left: none;
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin: 10px 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0 5px 20px;
|
||||
background-image: url(file.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li div.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- index page ------------------------------------------------------------ */
|
||||
|
||||
table.contentstable {
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.contentstable p.biglink {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
a.biglink {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.linkdescr {
|
||||
font-style: italic;
|
||||
padding-top: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
table.indextable > tbody > tr > td > ul {
|
||||
padding-left: 0em;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
div.genindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
/* -- domain module index --------------------------------------------------- */
|
||||
|
||||
table.modindextable td {
|
||||
padding: 2px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
div.body {
|
||||
min-width: 59em;
|
||||
max-width: 70em;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li, div.body blockquote {
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink,
|
||||
caption:hover > a.headerlink,
|
||||
p.caption:hover > a.headerlink,
|
||||
div.code-block-caption:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.body p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right {
|
||||
clear: right;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px 7px 0 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px 7px 0 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.admonition dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.body p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table caption span.caption-text {
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table.footnote td, table.footnote th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* -- figures --------------------------------------------------------------- */
|
||||
|
||||
div.figure {
|
||||
margin: 0.5em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
div.figure p.caption {
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-text {
|
||||
}
|
||||
|
||||
/* -- field list styles ----------------------------------------------------- */
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.field-name {
|
||||
-moz-hyphens: manual;
|
||||
-ms-hyphens: manual;
|
||||
-webkit-hyphens: manual;
|
||||
hyphens: manual;
|
||||
}
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
dt:target, span.highlighted {
|
||||
background-color: #fbe54e;
|
||||
}
|
||||
|
||||
rect.highlighted {
|
||||
fill: #fbe54e;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.sig-paren {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #ffa;
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
abbr, acronym {
|
||||
border-bottom: dotted 1px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
||||
}
|
||||
|
||||
span.pre {
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 5px 0px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
div.code-block-caption {
|
||||
padding: 2px 5px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
div.code-block-caption code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
div.code-block-caption + div > div.highlight > pre {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-number {
|
||||
padding: 0.1em 0.3em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-text {
|
||||
}
|
||||
|
||||
div.literal-block-wrapper {
|
||||
padding: 1em 1em 0;
|
||||
}
|
||||
|
||||
div.literal-block-wrapper div.highlight {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
code.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
code.xref, a code {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.viewcode-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
margin: -1px -10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.body div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
span.eqno a.headerlink {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.math:hover a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* -- printout stylesheet --------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
div.document,
|
||||
div.documentwrapper,
|
||||
div.bodywrapper {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar,
|
||||
div.related,
|
||||
div.footer,
|
||||
#top-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
BIN
help/en/_static/bg-page.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
help/en/_static/bullet_orange.png
Normal file
|
After Width: | Height: | Size: 165 B |
BIN
help/en/_static/comment-bright.png
Normal file
|
After Width: | Height: | Size: 756 B |
BIN
help/en/_static/comment-close.png
Normal file
|
After Width: | Height: | Size: 829 B |
BIN
help/en/_static/comment.png
Normal file
|
After Width: | Height: | Size: 641 B |
311
help/en/_static/doctools.js
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* doctools.js
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx JavaScript utilities for all documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* select a different prefix for underscore
|
||||
*/
|
||||
$u = _.noConflict();
|
||||
|
||||
/**
|
||||
* make the code below compatible with browsers without
|
||||
* an installed firebug like debugger
|
||||
if (!window.console || !console.firebug) {
|
||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
|
||||
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
|
||||
"profile", "profileEnd"];
|
||||
window.console = {};
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
window.console[names[i]] = function() {};
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* small helper function to urldecode strings
|
||||
*/
|
||||
jQuery.urldecode = function(x) {
|
||||
return decodeURIComponent(x).replace(/\+/g, ' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* small helper function to urlencode strings
|
||||
*/
|
||||
jQuery.urlencode = encodeURIComponent;
|
||||
|
||||
/**
|
||||
* This function returns the parsed url parameters of the
|
||||
* current request. Multiple values per key are supported,
|
||||
* it will always return arrays of strings for the value parts.
|
||||
*/
|
||||
jQuery.getQueryParameters = function(s) {
|
||||
if (typeof s === 'undefined')
|
||||
s = document.location.search;
|
||||
var parts = s.substr(s.indexOf('?') + 1).split('&');
|
||||
var result = {};
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var tmp = parts[i].split('=', 2);
|
||||
var key = jQuery.urldecode(tmp[0]);
|
||||
var value = jQuery.urldecode(tmp[1]);
|
||||
if (key in result)
|
||||
result[key].push(value);
|
||||
else
|
||||
result[key] = [value];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* highlight a given string on a jquery object by wrapping it in
|
||||
* span elements with the given class name.
|
||||
*/
|
||||
jQuery.fn.highlightText = function(text, className) {
|
||||
function highlight(node, addItems) {
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
var pos = val.toLowerCase().indexOf(text);
|
||||
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
|
||||
var span;
|
||||
var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
|
||||
if (isInSVG) {
|
||||
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
||||
} else {
|
||||
span = document.createElement("span");
|
||||
span.className = className;
|
||||
}
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
|
||||
document.createTextNode(val.substr(pos + text.length)),
|
||||
node.nextSibling));
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
if (isInSVG) {
|
||||
var bbox = span.getBBox();
|
||||
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
||||
rect.x.baseVal.value = bbox.x;
|
||||
rect.y.baseVal.value = bbox.y;
|
||||
rect.width.baseVal.value = bbox.width;
|
||||
rect.height.baseVal.value = bbox.height;
|
||||
rect.setAttribute('class', className);
|
||||
var parentOfText = node.parentNode.parentNode;
|
||||
addItems.push({
|
||||
"parent": node.parentNode,
|
||||
"target": rect});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!jQuery(node).is("button, select, textarea")) {
|
||||
jQuery.each(node.childNodes, function() {
|
||||
highlight(this, addItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
var addItems = [];
|
||||
var result = this.each(function() {
|
||||
highlight(this, addItems);
|
||||
});
|
||||
for (var i = 0; i < addItems.length; ++i) {
|
||||
jQuery(addItems[i].parent).before(addItems[i].target);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/*
|
||||
* backward compatibility for jQuery.browser
|
||||
* This will be supported until firefox bug is fixed.
|
||||
*/
|
||||
if (!jQuery.browser) {
|
||||
jQuery.uaMatch = function(ua) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(msie) ([\w.]+)/.exec(ua) ||
|
||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
browser: match[ 1 ] || "",
|
||||
version: match[ 2 ] || "0"
|
||||
};
|
||||
};
|
||||
jQuery.browser = {};
|
||||
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
var Documentation = {
|
||||
|
||||
init : function() {
|
||||
this.fixFirefoxAnchorBug();
|
||||
this.highlightSearchWords();
|
||||
this.initIndexTable();
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* i18n support
|
||||
*/
|
||||
TRANSLATIONS : {},
|
||||
PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
|
||||
LOCALE : 'unknown',
|
||||
|
||||
// gettext and ngettext don't access this so that the functions
|
||||
// can safely bound to a different name (_ = Documentation.gettext)
|
||||
gettext : function(string) {
|
||||
var translated = Documentation.TRANSLATIONS[string];
|
||||
if (typeof translated === 'undefined')
|
||||
return string;
|
||||
return (typeof translated === 'string') ? translated : translated[0];
|
||||
},
|
||||
|
||||
ngettext : function(singular, plural, n) {
|
||||
var translated = Documentation.TRANSLATIONS[singular];
|
||||
if (typeof translated === 'undefined')
|
||||
return (n == 1) ? singular : plural;
|
||||
return translated[Documentation.PLURALEXPR(n)];
|
||||
},
|
||||
|
||||
addTranslations : function(catalog) {
|
||||
for (var key in catalog.messages)
|
||||
this.TRANSLATIONS[key] = catalog.messages[key];
|
||||
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
|
||||
this.LOCALE = catalog.locale;
|
||||
},
|
||||
|
||||
/**
|
||||
* add context elements like header anchor links
|
||||
*/
|
||||
addContextElements : function() {
|
||||
$('div[id] > :header:first').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this headline')).
|
||||
appendTo(this);
|
||||
});
|
||||
$('dt[id]').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this definition')).
|
||||
appendTo(this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* workaround a firefox stupidity
|
||||
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
|
||||
*/
|
||||
fixFirefoxAnchorBug : function() {
|
||||
if (document.location.hash && $.browser.mozilla)
|
||||
window.setTimeout(function() {
|
||||
document.location.href += '';
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* highlight the search words provided in the url in the text
|
||||
*/
|
||||
highlightSearchWords : function() {
|
||||
var params = $.getQueryParameters();
|
||||
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
|
||||
if (terms.length) {
|
||||
var body = $('div.body');
|
||||
if (!body.length) {
|
||||
body = $('body');
|
||||
}
|
||||
window.setTimeout(function() {
|
||||
$.each(terms, function() {
|
||||
body.highlightText(this.toLowerCase(), 'highlighted');
|
||||
});
|
||||
}, 10);
|
||||
$('<p class="highlight-link"><a href="javascript:Documentation.' +
|
||||
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
|
||||
.appendTo($('#searchbox'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* init the domain index toggle buttons
|
||||
*/
|
||||
initIndexTable : function() {
|
||||
var togglers = $('img.toggler').click(function() {
|
||||
var src = $(this).attr('src');
|
||||
var idnum = $(this).attr('id').substr(7);
|
||||
$('tr.cg-' + idnum).toggle();
|
||||
if (src.substr(-9) === 'minus.png')
|
||||
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
|
||||
else
|
||||
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
|
||||
}).css('display', '');
|
||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
|
||||
togglers.click();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to hide the search marks again
|
||||
*/
|
||||
hideSearchWords : function() {
|
||||
$('#searchbox .highlight-link').fadeOut(300);
|
||||
$('span.highlighted').removeClass('highlighted');
|
||||
},
|
||||
|
||||
/**
|
||||
* make the url absolute
|
||||
*/
|
||||
makeURL : function(relativeURL) {
|
||||
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the current relative url
|
||||
*/
|
||||
getCurrentURL : function() {
|
||||
var path = document.location.pathname;
|
||||
var parts = path.split(/\//);
|
||||
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
|
||||
if (this === '..')
|
||||
parts.pop();
|
||||
});
|
||||
var url = parts.join('/');
|
||||
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
|
||||
},
|
||||
|
||||
initOnKeyListeners: function() {
|
||||
$(document).keyup(function(event) {
|
||||
var activeElementType = document.activeElement.tagName;
|
||||
// don't navigate when in search box or textarea
|
||||
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') {
|
||||
switch (event.keyCode) {
|
||||
case 37: // left
|
||||
var prevHref = $('link[rel="prev"]').prop('href');
|
||||
if (prevHref) {
|
||||
window.location.href = prevHref;
|
||||
return false;
|
||||
}
|
||||
case 39: // right
|
||||
var nextHref = $('link[rel="next"]').prop('href');
|
||||
if (nextHref) {
|
||||
window.location.href = nextHref;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// quick alias for translations
|
||||
_ = Documentation.gettext;
|
||||
|
||||
$(document).ready(function() {
|
||||
Documentation.init();
|
||||
});
|
||||
9
help/en/_static/documentation_options.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: '',
|
||||
VERSION: '4.0.3',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '.html',
|
||||
HAS_SOURCE: true,
|
||||
SOURCELINK_SUFFIX: '.txt'
|
||||
};
|
||||
BIN
help/en/_static/down-pressed.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
help/en/_static/down.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
help/en/_static/file.png
Normal file
|
After Width: | Height: | Size: 286 B |
376
help/en/_static/haiku.css
Normal file
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* haiku.css_t
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- haiku theme.
|
||||
*
|
||||
* Adapted from http://haiku-os.org/docs/Haiku-doc.css.
|
||||
* Original copyright message:
|
||||
*
|
||||
* Copyright 2008-2009, Haiku. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Francois Revol <revol@free.fr>
|
||||
* Stephan Assmus <superstippi@gmx.de>
|
||||
* Braden Ewing <brewin@gmail.com>
|
||||
* Humdinger <humdingerb@gmail.com>
|
||||
*
|
||||
* :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
html {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background: #FFF url(bg-page.png) top left repeat-x;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.5;
|
||||
margin: auto;
|
||||
padding: 0px;
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
min-width: 59em;
|
||||
max-width: 70em;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
padding: 8px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* link colors and text decoration */
|
||||
|
||||
a:link {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #dc3c01;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: #892601;
|
||||
}
|
||||
|
||||
a:hover, a:active {
|
||||
text-decoration: underline;
|
||||
color: #ff4500;
|
||||
}
|
||||
|
||||
/* Some headers act as anchors, don't give them a hover effect */
|
||||
|
||||
h1 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h2 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h3 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
h4 a:hover, a:active {
|
||||
text-decoration: none;
|
||||
color: #0c3762;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
color: #a7ce38;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #a7ce38;
|
||||
}
|
||||
|
||||
/* basic text elements */
|
||||
|
||||
div.content {
|
||||
margin-top: 20px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 50px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* heading and navigation */
|
||||
|
||||
div.header {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: 85px;
|
||||
/* background: #eeeeee; */
|
||||
padding: 0 40px;
|
||||
}
|
||||
div.header h1 {
|
||||
font-size: 1.6em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
color: #0c3762;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
div.header h1 a {
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
}
|
||||
div.header h2 {
|
||||
font-size: 1.3em;
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
color: #aaa;
|
||||
border: 0;
|
||||
margin-top: -3px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.header img.rightlogo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
div.title {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
div.topnav {
|
||||
/* background: #e0e0e0; */
|
||||
}
|
||||
div.topnav p {
|
||||
margin-top: 0;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 0px;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
div.bottomnav {
|
||||
background: #eeeeee;
|
||||
}
|
||||
div.bottomnav p {
|
||||
margin-right: 40px;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
a.uplink {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
/* contents box */
|
||||
|
||||
table.index {
|
||||
margin: 0px 0px 30px 30px;
|
||||
padding: 1px;
|
||||
border-width: 1px;
|
||||
border-style: dotted;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
table.index tr.heading {
|
||||
background-color: #e0e0e0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
table.index tr.index {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
table.index td {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
table.index a:link, table.index a:visited {
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: #dc3c01;
|
||||
}
|
||||
table.index a:hover, table.index a:active {
|
||||
text-decoration: underline;
|
||||
color: #ff4500;
|
||||
}
|
||||
|
||||
|
||||
/* Haiku User Guide styles and layout */
|
||||
|
||||
/* Rounded corner boxes */
|
||||
/* Common declarations */
|
||||
div.admonition {
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
border-color: #dcdcdc;
|
||||
padding: 10px 15px 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
div.note {
|
||||
padding: 10px 15px 10px 80px;
|
||||
background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;
|
||||
min-height: 42px;
|
||||
}
|
||||
div.warning {
|
||||
padding: 10px 15px 10px 80px;
|
||||
background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;
|
||||
min-height: 42px;
|
||||
}
|
||||
div.seealso {
|
||||
background: #e4ffde;
|
||||
}
|
||||
|
||||
/* More layout and styles */
|
||||
h1 {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
border-bottom: dotted thin #e0e0e0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.0em;
|
||||
font-weight: normal;
|
||||
color: #0c3762;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
div.content ul > li {
|
||||
-moz-background-clip:border;
|
||||
-moz-background-inline-policy:continuous;
|
||||
-moz-background-origin:padding;
|
||||
background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
padding: 0 0 0 1.666em;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #e2e2e2;
|
||||
font-size: 1.0em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
border-color: #0c3762;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 0.8em;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* printer only pretty stuff */
|
||||
@media print {
|
||||
.noprint {
|
||||
display: none;
|
||||
}
|
||||
/* for acronyms we want their definitions inlined at print time */
|
||||
acronym[title]:after {
|
||||
font-size: small;
|
||||
content: " (" attr(title) ")";
|
||||
font-style: italic;
|
||||
}
|
||||
/* and not have mozilla dotted underline */
|
||||
acronym {
|
||||
border: none;
|
||||
}
|
||||
div.topnav, div.bottomnav, div.header, table.index {
|
||||
display: none;
|
||||
}
|
||||
div.content {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
html {
|
||||
background: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
background-color: #f4debf;
|
||||
border-top: 1px solid #ac9;
|
||||
border-bottom: 1px solid #ac9;
|
||||
margin: -1px -10px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
/* math display */
|
||||
div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
10253
help/en/_static/jquery-3.2.1.js
vendored
Normal file
4
help/en/_static/jquery.js
vendored
Normal file
BIN
help/en/_static/minus.png
Normal file
|
After Width: | Height: | Size: 90 B |
BIN
help/en/_static/plus.png
Normal file
|
After Width: | Height: | Size: 90 B |
69
help/en/_static/pygments.css
Normal file
@@ -0,0 +1,69 @@
|
||||
.highlight .hll { background-color: #ffffcc }
|
||||
.highlight { background: #eeffcc; }
|
||||
.highlight .c { color: #408090; font-style: italic } /* Comment */
|
||||
.highlight .err { border: 1px solid #FF0000 } /* Error */
|
||||
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
|
||||
.highlight .o { color: #666666 } /* Operator */
|
||||
.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
|
||||
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #007020 } /* Comment.Preproc */
|
||||
.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
|
||||
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
|
||||
.highlight .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #FF0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.highlight .go { color: #333333 } /* Generic.Output */
|
||||
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.highlight .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #902000 } /* Keyword.Type */
|
||||
.highlight .m { color: #208050 } /* Literal.Number */
|
||||
.highlight .s { color: #4070a0 } /* Literal.String */
|
||||
.highlight .na { color: #4070a0 } /* Name.Attribute */
|
||||
.highlight .nb { color: #007020 } /* Name.Builtin */
|
||||
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #60add5 } /* Name.Constant */
|
||||
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
|
||||
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
|
||||
.highlight .ne { color: #007020 } /* Name.Exception */
|
||||
.highlight .nf { color: #06287e } /* Name.Function */
|
||||
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
|
||||
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
|
||||
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
|
||||
.highlight .nv { color: #bb60d5 } /* Name.Variable */
|
||||
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
|
||||
.highlight .mf { color: #208050 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
|
||||
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
|
||||
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
|
||||
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
|
||||
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
|
||||
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #235388 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
|
||||
.highlight .fm { color: #06287e } /* Name.Function.Magic */
|
||||
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
|
||||
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
|
||||
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
|
||||
761
help/en/_static/searchtools.js
Normal file
@@ -0,0 +1,761 @@
|
||||
/*
|
||||
* searchtools.js_t
|
||||
* ~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx JavaScript utilities for the full-text search.
|
||||
*
|
||||
* :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/* Non-minified version JS is _stemmer.js if file is provided */
|
||||
/**
|
||||
* Porter Stemmer
|
||||
*/
|
||||
var Stemmer = function() {
|
||||
|
||||
var step2list = {
|
||||
ational: 'ate',
|
||||
tional: 'tion',
|
||||
enci: 'ence',
|
||||
anci: 'ance',
|
||||
izer: 'ize',
|
||||
bli: 'ble',
|
||||
alli: 'al',
|
||||
entli: 'ent',
|
||||
eli: 'e',
|
||||
ousli: 'ous',
|
||||
ization: 'ize',
|
||||
ation: 'ate',
|
||||
ator: 'ate',
|
||||
alism: 'al',
|
||||
iveness: 'ive',
|
||||
fulness: 'ful',
|
||||
ousness: 'ous',
|
||||
aliti: 'al',
|
||||
iviti: 'ive',
|
||||
biliti: 'ble',
|
||||
logi: 'log'
|
||||
};
|
||||
|
||||
var step3list = {
|
||||
icate: 'ic',
|
||||
ative: '',
|
||||
alize: 'al',
|
||||
iciti: 'ic',
|
||||
ical: 'ic',
|
||||
ful: '',
|
||||
ness: ''
|
||||
};
|
||||
|
||||
var c = "[^aeiou]"; // consonant
|
||||
var v = "[aeiouy]"; // vowel
|
||||
var C = c + "[^aeiouy]*"; // consonant sequence
|
||||
var V = v + "[aeiou]*"; // vowel sequence
|
||||
|
||||
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
|
||||
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
|
||||
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
|
||||
var s_v = "^(" + C + ")?" + v; // vowel in stem
|
||||
|
||||
this.stemWord = function (w) {
|
||||
var stem;
|
||||
var suffix;
|
||||
var firstch;
|
||||
var origword = w;
|
||||
|
||||
if (w.length < 3)
|
||||
return w;
|
||||
|
||||
var re;
|
||||
var re2;
|
||||
var re3;
|
||||
var re4;
|
||||
|
||||
firstch = w.substr(0,1);
|
||||
if (firstch == "y")
|
||||
w = firstch.toUpperCase() + w.substr(1);
|
||||
|
||||
// Step 1a
|
||||
re = /^(.+?)(ss|i)es$/;
|
||||
re2 = /^(.+?)([^s])s$/;
|
||||
|
||||
if (re.test(w))
|
||||
w = w.replace(re,"$1$2");
|
||||
else if (re2.test(w))
|
||||
w = w.replace(re2,"$1$2");
|
||||
|
||||
// Step 1b
|
||||
re = /^(.+?)eed$/;
|
||||
re2 = /^(.+?)(ed|ing)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(fp[1])) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1];
|
||||
re2 = new RegExp(s_v);
|
||||
if (re2.test(stem)) {
|
||||
w = stem;
|
||||
re2 = /(at|bl|iz)$/;
|
||||
re3 = new RegExp("([^aeiouylsz])\\1$");
|
||||
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re2.test(w))
|
||||
w = w + "e";
|
||||
else if (re3.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
else if (re4.test(w))
|
||||
w = w + "e";
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1c
|
||||
re = /^(.+?)y$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(s_v);
|
||||
if (re.test(stem))
|
||||
w = stem + "i";
|
||||
}
|
||||
|
||||
// Step 2
|
||||
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step2list[suffix];
|
||||
}
|
||||
|
||||
// Step 3
|
||||
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
suffix = fp[2];
|
||||
re = new RegExp(mgr0);
|
||||
if (re.test(stem))
|
||||
w = stem + step3list[suffix];
|
||||
}
|
||||
|
||||
// Step 4
|
||||
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
|
||||
re2 = /^(.+?)(s|t)(ion)$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
if (re.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
else if (re2.test(w)) {
|
||||
var fp = re2.exec(w);
|
||||
stem = fp[1] + fp[2];
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re2.test(stem))
|
||||
w = stem;
|
||||
}
|
||||
|
||||
// Step 5
|
||||
re = /^(.+?)e$/;
|
||||
if (re.test(w)) {
|
||||
var fp = re.exec(w);
|
||||
stem = fp[1];
|
||||
re = new RegExp(mgr1);
|
||||
re2 = new RegExp(meq1);
|
||||
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
||||
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
|
||||
w = stem;
|
||||
}
|
||||
re = /ll$/;
|
||||
re2 = new RegExp(mgr1);
|
||||
if (re.test(w) && re2.test(w)) {
|
||||
re = /.$/;
|
||||
w = w.replace(re,"");
|
||||
}
|
||||
|
||||
// and turn initial Y back to y
|
||||
if (firstch == "y")
|
||||
w = firstch.toLowerCase() + w.substr(1);
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Simple result scoring code.
|
||||
*/
|
||||
var Scorer = {
|
||||
// Implement the following function to further tweak the score for each result
|
||||
// The function takes a result array [filename, title, anchor, descr, score]
|
||||
// and returns the new score.
|
||||
/*
|
||||
score: function(result) {
|
||||
return result[4];
|
||||
},
|
||||
*/
|
||||
|
||||
// query matches the full name of an object
|
||||
objNameMatch: 11,
|
||||
// or matches in the last dotted part of the object name
|
||||
objPartialMatch: 6,
|
||||
// Additive scores depending on the priority of the object
|
||||
objPrio: {0: 15, // used to be importantResults
|
||||
1: 5, // used to be objectResults
|
||||
2: -5}, // used to be unimportantResults
|
||||
// Used when the priority is not in the mapping.
|
||||
objPrioDefault: 0,
|
||||
|
||||
// query found in title
|
||||
title: 15,
|
||||
// query found in terms
|
||||
term: 5
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var splitChars = (function() {
|
||||
var result = {};
|
||||
var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648,
|
||||
1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702,
|
||||
2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971,
|
||||
2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345,
|
||||
3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761,
|
||||
3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823,
|
||||
4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125,
|
||||
8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695,
|
||||
11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587,
|
||||
43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141];
|
||||
var i, j, start, end;
|
||||
for (i = 0; i < singles.length; i++) {
|
||||
result[singles[i]] = true;
|
||||
}
|
||||
var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709],
|
||||
[722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161],
|
||||
[1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568],
|
||||
[1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807],
|
||||
[1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047],
|
||||
[2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383],
|
||||
[2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450],
|
||||
[2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547],
|
||||
[2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673],
|
||||
[2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820],
|
||||
[2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946],
|
||||
[2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023],
|
||||
[3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173],
|
||||
[3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332],
|
||||
[3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481],
|
||||
[3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718],
|
||||
[3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791],
|
||||
[3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095],
|
||||
[4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205],
|
||||
[4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687],
|
||||
[4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968],
|
||||
[4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869],
|
||||
[5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102],
|
||||
[6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271],
|
||||
[6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592],
|
||||
[6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822],
|
||||
[6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167],
|
||||
[7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959],
|
||||
[7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143],
|
||||
[8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318],
|
||||
[8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483],
|
||||
[8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101],
|
||||
[10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567],
|
||||
[11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292],
|
||||
[12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444],
|
||||
[12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783],
|
||||
[12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311],
|
||||
[19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511],
|
||||
[42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774],
|
||||
[42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071],
|
||||
[43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263],
|
||||
[43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519],
|
||||
[43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647],
|
||||
[43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967],
|
||||
[44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295],
|
||||
[57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274],
|
||||
[64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007],
|
||||
[65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381],
|
||||
[65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]];
|
||||
for (i = 0; i < ranges.length; i++) {
|
||||
start = ranges[i][0];
|
||||
end = ranges[i][1];
|
||||
for (j = start; j <= end; j++) {
|
||||
result[j] = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
|
||||
function splitQuery(query) {
|
||||
var result = [];
|
||||
var start = -1;
|
||||
for (var i = 0; i < query.length; i++) {
|
||||
if (splitChars[query.charCodeAt(i)]) {
|
||||
if (start !== -1) {
|
||||
result.push(query.slice(start, i));
|
||||
start = -1;
|
||||
}
|
||||
} else if (start === -1) {
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
if (start !== -1) {
|
||||
result.push(query.slice(start));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Search Module
|
||||
*/
|
||||
var Search = {
|
||||
|
||||
_index : null,
|
||||
_queued_query : null,
|
||||
_pulse_status : -1,
|
||||
|
||||
init : function() {
|
||||
var params = $.getQueryParameters();
|
||||
if (params.q) {
|
||||
var query = params.q[0];
|
||||
$('input[name="q"]')[0].value = query;
|
||||
this.performSearch(query);
|
||||
}
|
||||
},
|
||||
|
||||
loadIndex : function(url) {
|
||||
$.ajax({type: "GET", url: url, data: null,
|
||||
dataType: "script", cache: true,
|
||||
complete: function(jqxhr, textstatus) {
|
||||
if (textstatus != "success") {
|
||||
document.getElementById("searchindexloader").src = url;
|
||||
}
|
||||
}});
|
||||
},
|
||||
|
||||
setIndex : function(index) {
|
||||
var q;
|
||||
this._index = index;
|
||||
if ((q = this._queued_query) !== null) {
|
||||
this._queued_query = null;
|
||||
Search.query(q);
|
||||
}
|
||||
},
|
||||
|
||||
hasIndex : function() {
|
||||
return this._index !== null;
|
||||
},
|
||||
|
||||
deferQuery : function(query) {
|
||||
this._queued_query = query;
|
||||
},
|
||||
|
||||
stopPulse : function() {
|
||||
this._pulse_status = 0;
|
||||
},
|
||||
|
||||
startPulse : function() {
|
||||
if (this._pulse_status >= 0)
|
||||
return;
|
||||
function pulse() {
|
||||
var i;
|
||||
Search._pulse_status = (Search._pulse_status + 1) % 4;
|
||||
var dotString = '';
|
||||
for (i = 0; i < Search._pulse_status; i++)
|
||||
dotString += '.';
|
||||
Search.dots.text(dotString);
|
||||
if (Search._pulse_status > -1)
|
||||
window.setTimeout(pulse, 500);
|
||||
}
|
||||
pulse();
|
||||
},
|
||||
|
||||
/**
|
||||
* perform a search for something (or wait until index is loaded)
|
||||
*/
|
||||
performSearch : function(query) {
|
||||
// create the required interface elements
|
||||
this.out = $('#search-results');
|
||||
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
|
||||
this.dots = $('<span></span>').appendTo(this.title);
|
||||
this.status = $('<p style="display: none"></p>').appendTo(this.out);
|
||||
this.output = $('<ul class="search"/>').appendTo(this.out);
|
||||
|
||||
$('#search-progress').text(_('Preparing search...'));
|
||||
this.startPulse();
|
||||
|
||||
// index already loaded, the browser was quick!
|
||||
if (this.hasIndex())
|
||||
this.query(query);
|
||||
else
|
||||
this.deferQuery(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* execute search (requires search index to be loaded)
|
||||
*/
|
||||
query : function(query) {
|
||||
var i;
|
||||
var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"];
|
||||
|
||||
// stem the searchterms and add them to the correct list
|
||||
var stemmer = new Stemmer();
|
||||
var searchterms = [];
|
||||
var excluded = [];
|
||||
var hlterms = [];
|
||||
var tmp = splitQuery(query);
|
||||
var objectterms = [];
|
||||
for (i = 0; i < tmp.length; i++) {
|
||||
if (tmp[i] !== "") {
|
||||
objectterms.push(tmp[i].toLowerCase());
|
||||
}
|
||||
|
||||
if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
|
||||
tmp[i] === "") {
|
||||
// skip this "word"
|
||||
continue;
|
||||
}
|
||||
// stem the word
|
||||
var word = stemmer.stemWord(tmp[i].toLowerCase());
|
||||
// prevent stemmer from cutting word smaller than two chars
|
||||
if(word.length < 3 && tmp[i].length >= 3) {
|
||||
word = tmp[i];
|
||||
}
|
||||
var toAppend;
|
||||
// select the correct list
|
||||
if (word[0] == '-') {
|
||||
toAppend = excluded;
|
||||
word = word.substr(1);
|
||||
}
|
||||
else {
|
||||
toAppend = searchterms;
|
||||
hlterms.push(tmp[i].toLowerCase());
|
||||
}
|
||||
// only add if not already in the list
|
||||
if (!$u.contains(toAppend, word))
|
||||
toAppend.push(word);
|
||||
}
|
||||
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
|
||||
|
||||
// console.debug('SEARCH: searching for:');
|
||||
// console.info('required: ', searchterms);
|
||||
// console.info('excluded: ', excluded);
|
||||
|
||||
// prepare search
|
||||
var terms = this._index.terms;
|
||||
var titleterms = this._index.titleterms;
|
||||
|
||||
// array of [filename, title, anchor, descr, score]
|
||||
var results = [];
|
||||
$('#search-progress').empty();
|
||||
|
||||
// lookup as object
|
||||
for (i = 0; i < objectterms.length; i++) {
|
||||
var others = [].concat(objectterms.slice(0, i),
|
||||
objectterms.slice(i+1, objectterms.length));
|
||||
results = results.concat(this.performObjectSearch(objectterms[i], others));
|
||||
}
|
||||
|
||||
// lookup as search terms in fulltext
|
||||
results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
|
||||
|
||||
// let the scorer override scores with a custom scoring function
|
||||
if (Scorer.score) {
|
||||
for (i = 0; i < results.length; i++)
|
||||
results[i][4] = Scorer.score(results[i]);
|
||||
}
|
||||
|
||||
// now sort the results by score (in opposite order of appearance, since the
|
||||
// display function below uses pop() to retrieve items) and then
|
||||
// alphabetically
|
||||
results.sort(function(a, b) {
|
||||
var left = a[4];
|
||||
var right = b[4];
|
||||
if (left > right) {
|
||||
return 1;
|
||||
} else if (left < right) {
|
||||
return -1;
|
||||
} else {
|
||||
// same score: sort alphabetically
|
||||
left = a[1].toLowerCase();
|
||||
right = b[1].toLowerCase();
|
||||
return (left > right) ? -1 : ((left < right) ? 1 : 0);
|
||||
}
|
||||
});
|
||||
|
||||
// for debugging
|
||||
//Search.lastresults = results.slice(); // a copy
|
||||
//console.info('search results:', Search.lastresults);
|
||||
|
||||
// print the results
|
||||
var resultCount = results.length;
|
||||
function displayNextItem() {
|
||||
// results left, load the summary and display it
|
||||
if (results.length) {
|
||||
var item = results.pop();
|
||||
var listItem = $('<li style="display:none"></li>');
|
||||
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
|
||||
// dirhtml builder
|
||||
var dirname = item[0] + '/';
|
||||
if (dirname.match(/\/index\/$/)) {
|
||||
dirname = dirname.substring(0, dirname.length-6);
|
||||
} else if (dirname == 'index/') {
|
||||
dirname = '';
|
||||
}
|
||||
listItem.append($('<a/>').attr('href',
|
||||
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
} else {
|
||||
// normal html builders
|
||||
listItem.append($('<a/>').attr('href',
|
||||
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
}
|
||||
if (item[3]) {
|
||||
listItem.append($('<span> (' + item[3] + ')</span>'));
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
|
||||
var suffix = DOCUMENTATION_OPTIONS.SOURCELINK_SUFFIX;
|
||||
if (suffix === undefined) {
|
||||
suffix = '.txt';
|
||||
}
|
||||
$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix),
|
||||
dataType: "text",
|
||||
complete: function(jqxhr, textstatus) {
|
||||
var data = jqxhr.responseText;
|
||||
if (data !== '' && data !== undefined) {
|
||||
listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
|
||||
}
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
}});
|
||||
} else {
|
||||
// no source available, just display title
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
}
|
||||
}
|
||||
// search finished, update title and status message
|
||||
else {
|
||||
Search.stopPulse();
|
||||
Search.title.text(_('Search Results'));
|
||||
if (!resultCount)
|
||||
Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
|
||||
else
|
||||
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
|
||||
Search.status.fadeIn(500);
|
||||
}
|
||||
}
|
||||
displayNextItem();
|
||||
},
|
||||
|
||||
/**
|
||||
* search for object names
|
||||
*/
|
||||
performObjectSearch : function(object, otherterms) {
|
||||
var filenames = this._index.filenames;
|
||||
var docnames = this._index.docnames;
|
||||
var objects = this._index.objects;
|
||||
var objnames = this._index.objnames;
|
||||
var titles = this._index.titles;
|
||||
|
||||
var i;
|
||||
var results = [];
|
||||
|
||||
for (var prefix in objects) {
|
||||
for (var name in objects[prefix]) {
|
||||
var fullname = (prefix ? prefix + '.' : '') + name;
|
||||
if (fullname.toLowerCase().indexOf(object) > -1) {
|
||||
var score = 0;
|
||||
var parts = fullname.split('.');
|
||||
// check for different match types: exact matches of full name or
|
||||
// "last name" (i.e. last dotted part)
|
||||
if (fullname == object || parts[parts.length - 1] == object) {
|
||||
score += Scorer.objNameMatch;
|
||||
// matches in last name
|
||||
} else if (parts[parts.length - 1].indexOf(object) > -1) {
|
||||
score += Scorer.objPartialMatch;
|
||||
}
|
||||
var match = objects[prefix][name];
|
||||
var objname = objnames[match[1]][2];
|
||||
var title = titles[match[0]];
|
||||
// If more than one term searched for, we require other words to be
|
||||
// found in the name/title/description
|
||||
if (otherterms.length > 0) {
|
||||
var haystack = (prefix + ' ' + name + ' ' +
|
||||
objname + ' ' + title).toLowerCase();
|
||||
var allfound = true;
|
||||
for (i = 0; i < otherterms.length; i++) {
|
||||
if (haystack.indexOf(otherterms[i]) == -1) {
|
||||
allfound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allfound) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var descr = objname + _(', in ') + title;
|
||||
|
||||
var anchor = match[3];
|
||||
if (anchor === '')
|
||||
anchor = fullname;
|
||||
else if (anchor == '-')
|
||||
anchor = objnames[match[1]][1] + '-' + fullname;
|
||||
// add custom score for some objects according to scorer
|
||||
if (Scorer.objPrio.hasOwnProperty(match[2])) {
|
||||
score += Scorer.objPrio[match[2]];
|
||||
} else {
|
||||
score += Scorer.objPrioDefault;
|
||||
}
|
||||
results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* search for full-text terms in the index
|
||||
*/
|
||||
performTermsSearch : function(searchterms, excluded, terms, titleterms) {
|
||||
var docnames = this._index.docnames;
|
||||
var filenames = this._index.filenames;
|
||||
var titles = this._index.titles;
|
||||
|
||||
var i, j, file;
|
||||
var fileMap = {};
|
||||
var scoreMap = {};
|
||||
var results = [];
|
||||
|
||||
// perform the search on the required terms
|
||||
for (i = 0; i < searchterms.length; i++) {
|
||||
var word = searchterms[i];
|
||||
var files = [];
|
||||
var _o = [
|
||||
{files: terms[word], score: Scorer.term},
|
||||
{files: titleterms[word], score: Scorer.title}
|
||||
];
|
||||
|
||||
// no match but word was a required one
|
||||
if ($u.every(_o, function(o){return o.files === undefined;})) {
|
||||
break;
|
||||
}
|
||||
// found search word in contents
|
||||
$u.each(_o, function(o) {
|
||||
var _files = o.files;
|
||||
if (_files === undefined)
|
||||
return
|
||||
|
||||
if (_files.length === undefined)
|
||||
_files = [_files];
|
||||
files = files.concat(_files);
|
||||
|
||||
// set score for the word in each file to Scorer.term
|
||||
for (j = 0; j < _files.length; j++) {
|
||||
file = _files[j];
|
||||
if (!(file in scoreMap))
|
||||
scoreMap[file] = {}
|
||||
scoreMap[file][word] = o.score;
|
||||
}
|
||||
});
|
||||
|
||||
// create the mapping
|
||||
for (j = 0; j < files.length; j++) {
|
||||
file = files[j];
|
||||
if (file in fileMap)
|
||||
fileMap[file].push(word);
|
||||
else
|
||||
fileMap[file] = [word];
|
||||
}
|
||||
}
|
||||
|
||||
// now check if the files don't contain excluded terms
|
||||
for (file in fileMap) {
|
||||
var valid = true;
|
||||
|
||||
// check if all requirements are matched
|
||||
if (fileMap[file].length != searchterms.length)
|
||||
continue;
|
||||
|
||||
// ensure that none of the excluded terms is in the search result
|
||||
for (i = 0; i < excluded.length; i++) {
|
||||
if (terms[excluded[i]] == file ||
|
||||
titleterms[excluded[i]] == file ||
|
||||
$u.contains(terms[excluded[i]] || [], file) ||
|
||||
$u.contains(titleterms[excluded[i]] || [], file)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have still a valid result we can add it to the result list
|
||||
if (valid) {
|
||||
// select one (max) score for the file.
|
||||
// for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
|
||||
var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
|
||||
results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to return a node containing the
|
||||
* search summary for a given text. keywords is a list
|
||||
* of stemmed words, hlwords is the list of normal, unstemmed
|
||||
* words. the first one is used to find the occurrence, the
|
||||
* latter for highlighting it.
|
||||
*/
|
||||
makeSearchSummary : function(text, keywords, hlwords) {
|
||||
var textLower = text.toLowerCase();
|
||||
var start = 0;
|
||||
$.each(keywords, function() {
|
||||
var i = textLower.indexOf(this.toLowerCase());
|
||||
if (i > -1)
|
||||
start = i;
|
||||
});
|
||||
start = Math.max(start - 120, 0);
|
||||
var excerpt = ((start > 0) ? '...' : '') +
|
||||
$.trim(text.substr(start, 240)) +
|
||||
((start + 240 - text.length) ? '...' : '');
|
||||
var rv = $('<div class="context"></div>').text(excerpt);
|
||||
$.each(hlwords, function() {
|
||||
rv = rv.highlightText(this, 'highlighted');
|
||||
});
|
||||
return rv;
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
Search.init();
|
||||
});
|
||||
999
help/en/_static/underscore-1.3.1.js
Normal file
@@ -0,0 +1,999 @@
|
||||
// Underscore.js 1.3.1
|
||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
// Underscore is freely distributable under the MIT license.
|
||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
||||
// For all details and documentation:
|
||||
// http://documentcloud.github.com/underscore
|
||||
|
||||
(function() {
|
||||
|
||||
// Baseline setup
|
||||
// --------------
|
||||
|
||||
// Establish the root object, `window` in the browser, or `global` on the server.
|
||||
var root = this;
|
||||
|
||||
// Save the previous value of the `_` variable.
|
||||
var previousUnderscore = root._;
|
||||
|
||||
// Establish the object that gets returned to break out of a loop iteration.
|
||||
var breaker = {};
|
||||
|
||||
// Save bytes in the minified (but not gzipped) version:
|
||||
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
|
||||
|
||||
// Create quick reference variables for speed access to core prototypes.
|
||||
var slice = ArrayProto.slice,
|
||||
unshift = ArrayProto.unshift,
|
||||
toString = ObjProto.toString,
|
||||
hasOwnProperty = ObjProto.hasOwnProperty;
|
||||
|
||||
// All **ECMAScript 5** native function implementations that we hope to use
|
||||
// are declared here.
|
||||
var
|
||||
nativeForEach = ArrayProto.forEach,
|
||||
nativeMap = ArrayProto.map,
|
||||
nativeReduce = ArrayProto.reduce,
|
||||
nativeReduceRight = ArrayProto.reduceRight,
|
||||
nativeFilter = ArrayProto.filter,
|
||||
nativeEvery = ArrayProto.every,
|
||||
nativeSome = ArrayProto.some,
|
||||
nativeIndexOf = ArrayProto.indexOf,
|
||||
nativeLastIndexOf = ArrayProto.lastIndexOf,
|
||||
nativeIsArray = Array.isArray,
|
||||
nativeKeys = Object.keys,
|
||||
nativeBind = FuncProto.bind;
|
||||
|
||||
// Create a safe reference to the Underscore object for use below.
|
||||
var _ = function(obj) { return new wrapper(obj); };
|
||||
|
||||
// Export the Underscore object for **Node.js**, with
|
||||
// backwards-compatibility for the old `require()` API. If we're in
|
||||
// the browser, add `_` as a global object via a string identifier,
|
||||
// for Closure Compiler "advanced" mode.
|
||||
if (typeof exports !== 'undefined') {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
exports = module.exports = _;
|
||||
}
|
||||
exports._ = _;
|
||||
} else {
|
||||
root['_'] = _;
|
||||
}
|
||||
|
||||
// Current version.
|
||||
_.VERSION = '1.3.1';
|
||||
|
||||
// Collection Functions
|
||||
// --------------------
|
||||
|
||||
// The cornerstone, an `each` implementation, aka `forEach`.
|
||||
// Handles objects with the built-in `forEach`, arrays, and raw objects.
|
||||
// Delegates to **ECMAScript 5**'s native `forEach` if available.
|
||||
var each = _.each = _.forEach = function(obj, iterator, context) {
|
||||
if (obj == null) return;
|
||||
if (nativeForEach && obj.forEach === nativeForEach) {
|
||||
obj.forEach(iterator, context);
|
||||
} else if (obj.length === +obj.length) {
|
||||
for (var i = 0, l = obj.length; i < l; i++) {
|
||||
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
|
||||
}
|
||||
} else {
|
||||
for (var key in obj) {
|
||||
if (_.has(obj, key)) {
|
||||
if (iterator.call(context, obj[key], key, obj) === breaker) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Return the results of applying the iterator to each element.
|
||||
// Delegates to **ECMAScript 5**'s native `map` if available.
|
||||
_.map = _.collect = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
if (obj == null) return results;
|
||||
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
results[results.length] = iterator.call(context, value, index, list);
|
||||
});
|
||||
if (obj.length === +obj.length) results.length = obj.length;
|
||||
return results;
|
||||
};
|
||||
|
||||
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
||||
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
||||
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
||||
var initial = arguments.length > 2;
|
||||
if (obj == null) obj = [];
|
||||
if (nativeReduce && obj.reduce === nativeReduce) {
|
||||
if (context) iterator = _.bind(iterator, context);
|
||||
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
|
||||
}
|
||||
each(obj, function(value, index, list) {
|
||||
if (!initial) {
|
||||
memo = value;
|
||||
initial = true;
|
||||
} else {
|
||||
memo = iterator.call(context, memo, value, index, list);
|
||||
}
|
||||
});
|
||||
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
|
||||
return memo;
|
||||
};
|
||||
|
||||
// The right-associative version of reduce, also known as `foldr`.
|
||||
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
|
||||
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
|
||||
var initial = arguments.length > 2;
|
||||
if (obj == null) obj = [];
|
||||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
||||
if (context) iterator = _.bind(iterator, context);
|
||||
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
||||
}
|
||||
var reversed = _.toArray(obj).reverse();
|
||||
if (context && !initial) iterator = _.bind(iterator, context);
|
||||
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
|
||||
};
|
||||
|
||||
// Return the first value which passes a truth test. Aliased as `detect`.
|
||||
_.find = _.detect = function(obj, iterator, context) {
|
||||
var result;
|
||||
any(obj, function(value, index, list) {
|
||||
if (iterator.call(context, value, index, list)) {
|
||||
result = value;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Return all the elements that pass a truth test.
|
||||
// Delegates to **ECMAScript 5**'s native `filter` if available.
|
||||
// Aliased as `select`.
|
||||
_.filter = _.select = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
if (obj == null) return results;
|
||||
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
if (iterator.call(context, value, index, list)) results[results.length] = value;
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
// Return all the elements for which a truth test fails.
|
||||
_.reject = function(obj, iterator, context) {
|
||||
var results = [];
|
||||
if (obj == null) return results;
|
||||
each(obj, function(value, index, list) {
|
||||
if (!iterator.call(context, value, index, list)) results[results.length] = value;
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
// Determine whether all of the elements match a truth test.
|
||||
// Delegates to **ECMAScript 5**'s native `every` if available.
|
||||
// Aliased as `all`.
|
||||
_.every = _.all = function(obj, iterator, context) {
|
||||
var result = true;
|
||||
if (obj == null) return result;
|
||||
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Determine if at least one element in the object matches a truth test.
|
||||
// Delegates to **ECMAScript 5**'s native `some` if available.
|
||||
// Aliased as `any`.
|
||||
var any = _.some = _.any = function(obj, iterator, context) {
|
||||
iterator || (iterator = _.identity);
|
||||
var result = false;
|
||||
if (obj == null) return result;
|
||||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
||||
each(obj, function(value, index, list) {
|
||||
if (result || (result = iterator.call(context, value, index, list))) return breaker;
|
||||
});
|
||||
return !!result;
|
||||
};
|
||||
|
||||
// Determine if a given value is included in the array or object using `===`.
|
||||
// Aliased as `contains`.
|
||||
_.include = _.contains = function(obj, target) {
|
||||
var found = false;
|
||||
if (obj == null) return found;
|
||||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
||||
found = any(obj, function(value) {
|
||||
return value === target;
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
// Invoke a method (with arguments) on every item in a collection.
|
||||
_.invoke = function(obj, method) {
|
||||
var args = slice.call(arguments, 2);
|
||||
return _.map(obj, function(value) {
|
||||
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
|
||||
});
|
||||
};
|
||||
|
||||
// Convenience version of a common use case of `map`: fetching a property.
|
||||
_.pluck = function(obj, key) {
|
||||
return _.map(obj, function(value){ return value[key]; });
|
||||
};
|
||||
|
||||
// Return the maximum element or (element-based computation).
|
||||
_.max = function(obj, iterator, context) {
|
||||
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
|
||||
if (!iterator && _.isEmpty(obj)) return -Infinity;
|
||||
var result = {computed : -Infinity};
|
||||
each(obj, function(value, index, list) {
|
||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
computed >= result.computed && (result = {value : value, computed : computed});
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
// Return the minimum element (or element-based computation).
|
||||
_.min = function(obj, iterator, context) {
|
||||
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
|
||||
if (!iterator && _.isEmpty(obj)) return Infinity;
|
||||
var result = {computed : Infinity};
|
||||
each(obj, function(value, index, list) {
|
||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
||||
computed < result.computed && (result = {value : value, computed : computed});
|
||||
});
|
||||
return result.value;
|
||||
};
|
||||
|
||||
// Shuffle an array.
|
||||
_.shuffle = function(obj) {
|
||||
var shuffled = [], rand;
|
||||
each(obj, function(value, index, list) {
|
||||
if (index == 0) {
|
||||
shuffled[0] = value;
|
||||
} else {
|
||||
rand = Math.floor(Math.random() * (index + 1));
|
||||
shuffled[index] = shuffled[rand];
|
||||
shuffled[rand] = value;
|
||||
}
|
||||
});
|
||||
return shuffled;
|
||||
};
|
||||
|
||||
// Sort the object's values by a criterion produced by an iterator.
|
||||
_.sortBy = function(obj, iterator, context) {
|
||||
return _.pluck(_.map(obj, function(value, index, list) {
|
||||
return {
|
||||
value : value,
|
||||
criteria : iterator.call(context, value, index, list)
|
||||
};
|
||||
}).sort(function(left, right) {
|
||||
var a = left.criteria, b = right.criteria;
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}), 'value');
|
||||
};
|
||||
|
||||
// Groups the object's values by a criterion. Pass either a string attribute
|
||||
// to group by, or a function that returns the criterion.
|
||||
_.groupBy = function(obj, val) {
|
||||
var result = {};
|
||||
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
|
||||
each(obj, function(value, index) {
|
||||
var key = iterator(value, index);
|
||||
(result[key] || (result[key] = [])).push(value);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Use a comparator function to figure out at what index an object should
|
||||
// be inserted so as to maintain order. Uses binary search.
|
||||
_.sortedIndex = function(array, obj, iterator) {
|
||||
iterator || (iterator = _.identity);
|
||||
var low = 0, high = array.length;
|
||||
while (low < high) {
|
||||
var mid = (low + high) >> 1;
|
||||
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
|
||||
}
|
||||
return low;
|
||||
};
|
||||
|
||||
// Safely convert anything iterable into a real, live array.
|
||||
_.toArray = function(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
if (_.isArray(iterable)) return slice.call(iterable);
|
||||
if (_.isArguments(iterable)) return slice.call(iterable);
|
||||
return _.values(iterable);
|
||||
};
|
||||
|
||||
// Return the number of elements in an object.
|
||||
_.size = function(obj) {
|
||||
return _.toArray(obj).length;
|
||||
};
|
||||
|
||||
// Array Functions
|
||||
// ---------------
|
||||
|
||||
// Get the first element of an array. Passing **n** will return the first N
|
||||
// values in the array. Aliased as `head`. The **guard** check allows it to work
|
||||
// with `_.map`.
|
||||
_.first = _.head = function(array, n, guard) {
|
||||
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
||||
};
|
||||
|
||||
// Returns everything but the last entry of the array. Especcialy useful on
|
||||
// the arguments object. Passing **n** will return all the values in
|
||||
// the array, excluding the last N. The **guard** check allows it to work with
|
||||
// `_.map`.
|
||||
_.initial = function(array, n, guard) {
|
||||
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
|
||||
};
|
||||
|
||||
// Get the last element of an array. Passing **n** will return the last N
|
||||
// values in the array. The **guard** check allows it to work with `_.map`.
|
||||
_.last = function(array, n, guard) {
|
||||
if ((n != null) && !guard) {
|
||||
return slice.call(array, Math.max(array.length - n, 0));
|
||||
} else {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
};
|
||||
|
||||
// Returns everything but the first entry of the array. Aliased as `tail`.
|
||||
// Especially useful on the arguments object. Passing an **index** will return
|
||||
// the rest of the values in the array from that index onward. The **guard**
|
||||
// check allows it to work with `_.map`.
|
||||
_.rest = _.tail = function(array, index, guard) {
|
||||
return slice.call(array, (index == null) || guard ? 1 : index);
|
||||
};
|
||||
|
||||
// Trim out all falsy values from an array.
|
||||
_.compact = function(array) {
|
||||
return _.filter(array, function(value){ return !!value; });
|
||||
};
|
||||
|
||||
// Return a completely flattened version of an array.
|
||||
_.flatten = function(array, shallow) {
|
||||
return _.reduce(array, function(memo, value) {
|
||||
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
|
||||
memo[memo.length] = value;
|
||||
return memo;
|
||||
}, []);
|
||||
};
|
||||
|
||||
// Return a version of the array that does not contain the specified value(s).
|
||||
_.without = function(array) {
|
||||
return _.difference(array, slice.call(arguments, 1));
|
||||
};
|
||||
|
||||
// Produce a duplicate-free version of the array. If the array has already
|
||||
// been sorted, you have the option of using a faster algorithm.
|
||||
// Aliased as `unique`.
|
||||
_.uniq = _.unique = function(array, isSorted, iterator) {
|
||||
var initial = iterator ? _.map(array, iterator) : array;
|
||||
var result = [];
|
||||
_.reduce(initial, function(memo, el, i) {
|
||||
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
|
||||
memo[memo.length] = el;
|
||||
result[result.length] = array[i];
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
return result;
|
||||
};
|
||||
|
||||
// Produce an array that contains the union: each distinct element from all of
|
||||
// the passed-in arrays.
|
||||
_.union = function() {
|
||||
return _.uniq(_.flatten(arguments, true));
|
||||
};
|
||||
|
||||
// Produce an array that contains every item shared between all the
|
||||
// passed-in arrays. (Aliased as "intersect" for back-compat.)
|
||||
_.intersection = _.intersect = function(array) {
|
||||
var rest = slice.call(arguments, 1);
|
||||
return _.filter(_.uniq(array), function(item) {
|
||||
return _.every(rest, function(other) {
|
||||
return _.indexOf(other, item) >= 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Take the difference between one array and a number of other arrays.
|
||||
// Only the elements present in just the first array will remain.
|
||||
_.difference = function(array) {
|
||||
var rest = _.flatten(slice.call(arguments, 1));
|
||||
return _.filter(array, function(value){ return !_.include(rest, value); });
|
||||
};
|
||||
|
||||
// Zip together multiple lists into a single array -- elements that share
|
||||
// an index go together.
|
||||
_.zip = function() {
|
||||
var args = slice.call(arguments);
|
||||
var length = _.max(_.pluck(args, 'length'));
|
||||
var results = new Array(length);
|
||||
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
|
||||
return results;
|
||||
};
|
||||
|
||||
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
|
||||
// we need this function. Return the position of the first occurrence of an
|
||||
// item in an array, or -1 if the item is not included in the array.
|
||||
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
|
||||
// If the array is large and already in sort order, pass `true`
|
||||
// for **isSorted** to use binary search.
|
||||
_.indexOf = function(array, item, isSorted) {
|
||||
if (array == null) return -1;
|
||||
var i, l;
|
||||
if (isSorted) {
|
||||
i = _.sortedIndex(array, item);
|
||||
return array[i] === item ? i : -1;
|
||||
}
|
||||
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
||||
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
|
||||
_.lastIndexOf = function(array, item) {
|
||||
if (array == null) return -1;
|
||||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
||||
var i = array.length;
|
||||
while (i--) if (i in array && array[i] === item) return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Generate an integer Array containing an arithmetic progression. A port of
|
||||
// the native Python `range()` function. See
|
||||
// [the Python documentation](http://docs.python.org/library/functions.html#range).
|
||||
_.range = function(start, stop, step) {
|
||||
if (arguments.length <= 1) {
|
||||
stop = start || 0;
|
||||
start = 0;
|
||||
}
|
||||
step = arguments[2] || 1;
|
||||
|
||||
var len = Math.max(Math.ceil((stop - start) / step), 0);
|
||||
var idx = 0;
|
||||
var range = new Array(len);
|
||||
|
||||
while(idx < len) {
|
||||
range[idx++] = start;
|
||||
start += step;
|
||||
}
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
// Function (ahem) Functions
|
||||
// ------------------
|
||||
|
||||
// Reusable constructor function for prototype setting.
|
||||
var ctor = function(){};
|
||||
|
||||
// Create a function bound to a given object (assigning `this`, and arguments,
|
||||
// optionally). Binding with arguments is also known as `curry`.
|
||||
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
|
||||
// We check for `func.bind` first, to fail fast when `func` is undefined.
|
||||
_.bind = function bind(func, context) {
|
||||
var bound, args;
|
||||
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
||||
if (!_.isFunction(func)) throw new TypeError;
|
||||
args = slice.call(arguments, 2);
|
||||
return bound = function() {
|
||||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
|
||||
ctor.prototype = func.prototype;
|
||||
var self = new ctor;
|
||||
var result = func.apply(self, args.concat(slice.call(arguments)));
|
||||
if (Object(result) === result) return result;
|
||||
return self;
|
||||
};
|
||||
};
|
||||
|
||||
// Bind all of an object's methods to that object. Useful for ensuring that
|
||||
// all callbacks defined on an object belong to it.
|
||||
_.bindAll = function(obj) {
|
||||
var funcs = slice.call(arguments, 1);
|
||||
if (funcs.length == 0) funcs = _.functions(obj);
|
||||
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Memoize an expensive function by storing its results.
|
||||
_.memoize = function(func, hasher) {
|
||||
var memo = {};
|
||||
hasher || (hasher = _.identity);
|
||||
return function() {
|
||||
var key = hasher.apply(this, arguments);
|
||||
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
|
||||
};
|
||||
};
|
||||
|
||||
// Delays a function for the given number of milliseconds, and then calls
|
||||
// it with the arguments supplied.
|
||||
_.delay = function(func, wait) {
|
||||
var args = slice.call(arguments, 2);
|
||||
return setTimeout(function(){ return func.apply(func, args); }, wait);
|
||||
};
|
||||
|
||||
// Defers a function, scheduling it to run after the current call stack has
|
||||
// cleared.
|
||||
_.defer = function(func) {
|
||||
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
|
||||
};
|
||||
|
||||
// Returns a function, that, when invoked, will only be triggered at most once
|
||||
// during a given window of time.
|
||||
_.throttle = function(func, wait) {
|
||||
var context, args, timeout, throttling, more;
|
||||
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
|
||||
return function() {
|
||||
context = this; args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (more) func.apply(context, args);
|
||||
whenDone();
|
||||
};
|
||||
if (!timeout) timeout = setTimeout(later, wait);
|
||||
if (throttling) {
|
||||
more = true;
|
||||
} else {
|
||||
func.apply(context, args);
|
||||
}
|
||||
whenDone();
|
||||
throttling = true;
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function, that, as long as it continues to be invoked, will not
|
||||
// be triggered. The function will be called after it stops being called for
|
||||
// N milliseconds.
|
||||
_.debounce = function(func, wait) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
func.apply(context, args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function that will be executed at most one time, no matter how
|
||||
// often you call it. Useful for lazy initialization.
|
||||
_.once = function(func) {
|
||||
var ran = false, memo;
|
||||
return function() {
|
||||
if (ran) return memo;
|
||||
ran = true;
|
||||
return memo = func.apply(this, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns the first function passed as an argument to the second,
|
||||
// allowing you to adjust arguments, run code before and after, and
|
||||
// conditionally execute the original function.
|
||||
_.wrap = function(func, wrapper) {
|
||||
return function() {
|
||||
var args = [func].concat(slice.call(arguments, 0));
|
||||
return wrapper.apply(this, args);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function that is the composition of a list of functions, each
|
||||
// consuming the return value of the function that follows.
|
||||
_.compose = function() {
|
||||
var funcs = arguments;
|
||||
return function() {
|
||||
var args = arguments;
|
||||
for (var i = funcs.length - 1; i >= 0; i--) {
|
||||
args = [funcs[i].apply(this, args)];
|
||||
}
|
||||
return args[0];
|
||||
};
|
||||
};
|
||||
|
||||
// Returns a function that will only be executed after being called N times.
|
||||
_.after = function(times, func) {
|
||||
if (times <= 0) return func();
|
||||
return function() {
|
||||
if (--times < 1) { return func.apply(this, arguments); }
|
||||
};
|
||||
};
|
||||
|
||||
// Object Functions
|
||||
// ----------------
|
||||
|
||||
// Retrieve the names of an object's properties.
|
||||
// Delegates to **ECMAScript 5**'s native `Object.keys`
|
||||
_.keys = nativeKeys || function(obj) {
|
||||
if (obj !== Object(obj)) throw new TypeError('Invalid object');
|
||||
var keys = [];
|
||||
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
|
||||
return keys;
|
||||
};
|
||||
|
||||
// Retrieve the values of an object's properties.
|
||||
_.values = function(obj) {
|
||||
return _.map(obj, _.identity);
|
||||
};
|
||||
|
||||
// Return a sorted list of the function names available on the object.
|
||||
// Aliased as `methods`
|
||||
_.functions = _.methods = function(obj) {
|
||||
var names = [];
|
||||
for (var key in obj) {
|
||||
if (_.isFunction(obj[key])) names.push(key);
|
||||
}
|
||||
return names.sort();
|
||||
};
|
||||
|
||||
// Extend a given object with all the properties in passed-in object(s).
|
||||
_.extend = function(obj) {
|
||||
each(slice.call(arguments, 1), function(source) {
|
||||
for (var prop in source) {
|
||||
obj[prop] = source[prop];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Fill in a given object with default properties.
|
||||
_.defaults = function(obj) {
|
||||
each(slice.call(arguments, 1), function(source) {
|
||||
for (var prop in source) {
|
||||
if (obj[prop] == null) obj[prop] = source[prop];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Create a (shallow-cloned) duplicate of an object.
|
||||
_.clone = function(obj) {
|
||||
if (!_.isObject(obj)) return obj;
|
||||
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
|
||||
};
|
||||
|
||||
// Invokes interceptor with the obj, and then returns obj.
|
||||
// The primary purpose of this method is to "tap into" a method chain, in
|
||||
// order to perform operations on intermediate results within the chain.
|
||||
_.tap = function(obj, interceptor) {
|
||||
interceptor(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Internal recursive comparison function.
|
||||
function eq(a, b, stack) {
|
||||
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
|
||||
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
||||
// A strict comparison is necessary because `null == undefined`.
|
||||
if (a == null || b == null) return a === b;
|
||||
// Unwrap any wrapped objects.
|
||||
if (a._chain) a = a._wrapped;
|
||||
if (b._chain) b = b._wrapped;
|
||||
// Invoke a custom `isEqual` method if one is provided.
|
||||
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
||||
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
||||
// Compare `[[Class]]` names.
|
||||
var className = toString.call(a);
|
||||
if (className != toString.call(b)) return false;
|
||||
switch (className) {
|
||||
// Strings, numbers, dates, and booleans are compared by value.
|
||||
case '[object String]':
|
||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||
// equivalent to `new String("5")`.
|
||||
return a == String(b);
|
||||
case '[object Number]':
|
||||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
||||
// other numeric values.
|
||||
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
|
||||
case '[object Date]':
|
||||
case '[object Boolean]':
|
||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||
// millisecond representations. Note that invalid dates with millisecond representations
|
||||
// of `NaN` are not equivalent.
|
||||
return +a == +b;
|
||||
// RegExps are compared by their source patterns and flags.
|
||||
case '[object RegExp]':
|
||||
return a.source == b.source &&
|
||||
a.global == b.global &&
|
||||
a.multiline == b.multiline &&
|
||||
a.ignoreCase == b.ignoreCase;
|
||||
}
|
||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||
var length = stack.length;
|
||||
while (length--) {
|
||||
// Linear search. Performance is inversely proportional to the number of
|
||||
// unique nested structures.
|
||||
if (stack[length] == a) return true;
|
||||
}
|
||||
// Add the first object to the stack of traversed objects.
|
||||
stack.push(a);
|
||||
var size = 0, result = true;
|
||||
// Recursively compare objects and arrays.
|
||||
if (className == '[object Array]') {
|
||||
// Compare array lengths to determine if a deep comparison is necessary.
|
||||
size = a.length;
|
||||
result = size == b.length;
|
||||
if (result) {
|
||||
// Deep compare the contents, ignoring non-numeric properties.
|
||||
while (size--) {
|
||||
// Ensure commutative equality for sparse arrays.
|
||||
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Objects with different constructors are not equivalent.
|
||||
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
|
||||
// Deep compare objects.
|
||||
for (var key in a) {
|
||||
if (_.has(a, key)) {
|
||||
// Count the expected number of properties.
|
||||
size++;
|
||||
// Deep compare each member.
|
||||
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
|
||||
}
|
||||
}
|
||||
// Ensure that both objects contain the same number of properties.
|
||||
if (result) {
|
||||
for (key in b) {
|
||||
if (_.has(b, key) && !(size--)) break;
|
||||
}
|
||||
result = !size;
|
||||
}
|
||||
}
|
||||
// Remove the first object from the stack of traversed objects.
|
||||
stack.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Perform a deep comparison to check if two objects are equal.
|
||||
_.isEqual = function(a, b) {
|
||||
return eq(a, b, []);
|
||||
};
|
||||
|
||||
// Is a given array, string, or object empty?
|
||||
// An "empty" object has no enumerable own-properties.
|
||||
_.isEmpty = function(obj) {
|
||||
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
|
||||
for (var key in obj) if (_.has(obj, key)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Is a given value a DOM element?
|
||||
_.isElement = function(obj) {
|
||||
return !!(obj && obj.nodeType == 1);
|
||||
};
|
||||
|
||||
// Is a given value an array?
|
||||
// Delegates to ECMA5's native Array.isArray
|
||||
_.isArray = nativeIsArray || function(obj) {
|
||||
return toString.call(obj) == '[object Array]';
|
||||
};
|
||||
|
||||
// Is a given variable an object?
|
||||
_.isObject = function(obj) {
|
||||
return obj === Object(obj);
|
||||
};
|
||||
|
||||
// Is a given variable an arguments object?
|
||||
_.isArguments = function(obj) {
|
||||
return toString.call(obj) == '[object Arguments]';
|
||||
};
|
||||
if (!_.isArguments(arguments)) {
|
||||
_.isArguments = function(obj) {
|
||||
return !!(obj && _.has(obj, 'callee'));
|
||||
};
|
||||
}
|
||||
|
||||
// Is a given value a function?
|
||||
_.isFunction = function(obj) {
|
||||
return toString.call(obj) == '[object Function]';
|
||||
};
|
||||
|
||||
// Is a given value a string?
|
||||
_.isString = function(obj) {
|
||||
return toString.call(obj) == '[object String]';
|
||||
};
|
||||
|
||||
// Is a given value a number?
|
||||
_.isNumber = function(obj) {
|
||||
return toString.call(obj) == '[object Number]';
|
||||
};
|
||||
|
||||
// Is the given value `NaN`?
|
||||
_.isNaN = function(obj) {
|
||||
// `NaN` is the only value for which `===` is not reflexive.
|
||||
return obj !== obj;
|
||||
};
|
||||
|
||||
// Is a given value a boolean?
|
||||
_.isBoolean = function(obj) {
|
||||
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
|
||||
};
|
||||
|
||||
// Is a given value a date?
|
||||
_.isDate = function(obj) {
|
||||
return toString.call(obj) == '[object Date]';
|
||||
};
|
||||
|
||||
// Is the given value a regular expression?
|
||||
_.isRegExp = function(obj) {
|
||||
return toString.call(obj) == '[object RegExp]';
|
||||
};
|
||||
|
||||
// Is a given value equal to null?
|
||||
_.isNull = function(obj) {
|
||||
return obj === null;
|
||||
};
|
||||
|
||||
// Is a given variable undefined?
|
||||
_.isUndefined = function(obj) {
|
||||
return obj === void 0;
|
||||
};
|
||||
|
||||
// Has own property?
|
||||
_.has = function(obj, key) {
|
||||
return hasOwnProperty.call(obj, key);
|
||||
};
|
||||
|
||||
// Utility Functions
|
||||
// -----------------
|
||||
|
||||
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
|
||||
// previous owner. Returns a reference to the Underscore object.
|
||||
_.noConflict = function() {
|
||||
root._ = previousUnderscore;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Keep the identity function around for default iterators.
|
||||
_.identity = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
// Run a function **n** times.
|
||||
_.times = function (n, iterator, context) {
|
||||
for (var i = 0; i < n; i++) iterator.call(context, i);
|
||||
};
|
||||
|
||||
// Escape a string for HTML interpolation.
|
||||
_.escape = function(string) {
|
||||
return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
|
||||
};
|
||||
|
||||
// Add your own custom functions to the Underscore object, ensuring that
|
||||
// they're correctly added to the OOP wrapper as well.
|
||||
_.mixin = function(obj) {
|
||||
each(_.functions(obj), function(name){
|
||||
addToWrapper(name, _[name] = obj[name]);
|
||||
});
|
||||
};
|
||||
|
||||
// Generate a unique integer id (unique within the entire client session).
|
||||
// Useful for temporary DOM ids.
|
||||
var idCounter = 0;
|
||||
_.uniqueId = function(prefix) {
|
||||
var id = idCounter++;
|
||||
return prefix ? prefix + id : id;
|
||||
};
|
||||
|
||||
// By default, Underscore uses ERB-style template delimiters, change the
|
||||
// following template settings to use alternative delimiters.
|
||||
_.templateSettings = {
|
||||
evaluate : /<%([\s\S]+?)%>/g,
|
||||
interpolate : /<%=([\s\S]+?)%>/g,
|
||||
escape : /<%-([\s\S]+?)%>/g
|
||||
};
|
||||
|
||||
// When customizing `templateSettings`, if you don't want to define an
|
||||
// interpolation, evaluation or escaping regex, we need one that is
|
||||
// guaranteed not to match.
|
||||
var noMatch = /.^/;
|
||||
|
||||
// Within an interpolation, evaluation, or escaping, remove HTML escaping
|
||||
// that had been previously added.
|
||||
var unescape = function(code) {
|
||||
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
|
||||
};
|
||||
|
||||
// JavaScript micro-templating, similar to John Resig's implementation.
|
||||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
||||
// and correctly escapes quotes within interpolated code.
|
||||
_.template = function(str, data) {
|
||||
var c = _.templateSettings;
|
||||
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
|
||||
'with(obj||{}){__p.push(\'' +
|
||||
str.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(c.escape || noMatch, function(match, code) {
|
||||
return "',_.escape(" + unescape(code) + "),'";
|
||||
})
|
||||
.replace(c.interpolate || noMatch, function(match, code) {
|
||||
return "'," + unescape(code) + ",'";
|
||||
})
|
||||
.replace(c.evaluate || noMatch, function(match, code) {
|
||||
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
|
||||
})
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\t/g, '\\t')
|
||||
+ "');}return __p.join('');";
|
||||
var func = new Function('obj', '_', tmpl);
|
||||
if (data) return func(data, _);
|
||||
return function(data) {
|
||||
return func.call(this, data, _);
|
||||
};
|
||||
};
|
||||
|
||||
// Add a "chain" function, which will delegate to the wrapper.
|
||||
_.chain = function(obj) {
|
||||
return _(obj).chain();
|
||||
};
|
||||
|
||||
// The OOP Wrapper
|
||||
// ---------------
|
||||
|
||||
// If Underscore is called as a function, it returns a wrapped object that
|
||||
// can be used OO-style. This wrapper holds altered versions of all the
|
||||
// underscore functions. Wrapped objects may be chained.
|
||||
var wrapper = function(obj) { this._wrapped = obj; };
|
||||
|
||||
// Expose `wrapper.prototype` as `_.prototype`
|
||||
_.prototype = wrapper.prototype;
|
||||
|
||||
// Helper function to continue chaining intermediate results.
|
||||
var result = function(obj, chain) {
|
||||
return chain ? _(obj).chain() : obj;
|
||||
};
|
||||
|
||||
// A method to easily add functions to the OOP wrapper.
|
||||
var addToWrapper = function(name, func) {
|
||||
wrapper.prototype[name] = function() {
|
||||
var args = slice.call(arguments);
|
||||
unshift.call(args, this._wrapped);
|
||||
return result(func.apply(_, args), this._chain);
|
||||
};
|
||||
};
|
||||
|
||||
// Add all of the Underscore functions to the wrapper object.
|
||||
_.mixin(_);
|
||||
|
||||
// Add all mutator Array functions to the wrapper.
|
||||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
||||
var method = ArrayProto[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
var wrapped = this._wrapped;
|
||||
method.apply(wrapped, arguments);
|
||||
var length = wrapped.length;
|
||||
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
|
||||
return result(wrapped, this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
// Add all accessor Array functions to the wrapper.
|
||||
each(['concat', 'join', 'slice'], function(name) {
|
||||
var method = ArrayProto[name];
|
||||
wrapper.prototype[name] = function() {
|
||||
return result(method.apply(this._wrapped, arguments), this._chain);
|
||||
};
|
||||
});
|
||||
|
||||
// Start chaining a wrapped Underscore object.
|
||||
wrapper.prototype.chain = function() {
|
||||
this._chain = true;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Extracts the result from a wrapped and chained object.
|
||||
wrapper.prototype.value = function() {
|
||||
return this._wrapped;
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
31
help/en/_static/underscore.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// Underscore.js 1.3.1
|
||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||
// Underscore is freely distributable under the MIT license.
|
||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
||||
// For all details and documentation:
|
||||
// http://documentcloud.github.com/underscore
|
||||
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
|
||||
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
|
||||
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
|
||||
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
|
||||
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
|
||||
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
|
||||
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
|
||||
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
|
||||
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
|
||||
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
|
||||
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
|
||||
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
|
||||
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
|
||||
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
|
||||
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
|
||||
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
|
||||
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
|
||||
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
|
||||
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
|
||||
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),
|
||||
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
|
||||
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
|
||||
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
|
||||
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
|
||||
BIN
help/en/_static/up-pressed.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
help/en/_static/up.png
Normal file
|
After Width: | Height: | Size: 203 B |
808
help/en/_static/websupport.js
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* websupport.js
|
||||
* ~~~~~~~~~~~~~
|
||||
*
|
||||
* sphinx.websupport utilities for all documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.fn.autogrow = function() {
|
||||
return this.each(function() {
|
||||
var textarea = this;
|
||||
|
||||
$.fn.autogrow.resize(textarea);
|
||||
|
||||
$(textarea)
|
||||
.focus(function() {
|
||||
textarea.interval = setInterval(function() {
|
||||
$.fn.autogrow.resize(textarea);
|
||||
}, 500);
|
||||
})
|
||||
.blur(function() {
|
||||
clearInterval(textarea.interval);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.autogrow.resize = function(textarea) {
|
||||
var lineHeight = parseInt($(textarea).css('line-height'), 10);
|
||||
var lines = textarea.value.split('\n');
|
||||
var columns = textarea.cols;
|
||||
var lineCount = 0;
|
||||
$.each(lines, function() {
|
||||
lineCount += Math.ceil(this.length / columns) || 1;
|
||||
});
|
||||
var height = lineHeight * (lineCount + 1);
|
||||
$(textarea).css('height', height);
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
(function($) {
|
||||
var comp, by;
|
||||
|
||||
function init() {
|
||||
initEvents();
|
||||
initComparator();
|
||||
}
|
||||
|
||||
function initEvents() {
|
||||
$(document).on("click", 'a.comment-close', function(event) {
|
||||
event.preventDefault();
|
||||
hide($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.vote', function(event) {
|
||||
event.preventDefault();
|
||||
handleVote($(this));
|
||||
});
|
||||
$(document).on("click", 'a.reply', function(event) {
|
||||
event.preventDefault();
|
||||
openReply($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.close-reply', function(event) {
|
||||
event.preventDefault();
|
||||
closeReply($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.sort-option', function(event) {
|
||||
event.preventDefault();
|
||||
handleReSort($(this));
|
||||
});
|
||||
$(document).on("click", 'a.show-proposal', function(event) {
|
||||
event.preventDefault();
|
||||
showProposal($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.hide-proposal', function(event) {
|
||||
event.preventDefault();
|
||||
hideProposal($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.show-propose-change', function(event) {
|
||||
event.preventDefault();
|
||||
showProposeChange($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.hide-propose-change', function(event) {
|
||||
event.preventDefault();
|
||||
hideProposeChange($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.accept-comment', function(event) {
|
||||
event.preventDefault();
|
||||
acceptComment($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.delete-comment', function(event) {
|
||||
event.preventDefault();
|
||||
deleteComment($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.comment-markup', function(event) {
|
||||
event.preventDefault();
|
||||
toggleCommentMarkupBox($(this).attr('id').substring(2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comp, which is a comparator function used for sorting and
|
||||
* inserting comments into the list.
|
||||
*/
|
||||
function setComparator() {
|
||||
// If the first three letters are "asc", sort in ascending order
|
||||
// and remove the prefix.
|
||||
if (by.substring(0,3) == 'asc') {
|
||||
var i = by.substring(3);
|
||||
comp = function(a, b) { return a[i] - b[i]; };
|
||||
} else {
|
||||
// Otherwise sort in descending order.
|
||||
comp = function(a, b) { return b[by] - a[by]; };
|
||||
}
|
||||
|
||||
// Reset link styles and format the selected sort option.
|
||||
$('a.sel').attr('href', '#').removeClass('sel');
|
||||
$('a.by' + by).removeAttr('href').addClass('sel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a comp function. If the user has preferences stored in
|
||||
* the sortBy cookie, use those, otherwise use the default.
|
||||
*/
|
||||
function initComparator() {
|
||||
by = 'rating'; // Default to sort by rating.
|
||||
// If the sortBy cookie is set, use that instead.
|
||||
if (document.cookie.length > 0) {
|
||||
var start = document.cookie.indexOf('sortBy=');
|
||||
if (start != -1) {
|
||||
start = start + 7;
|
||||
var end = document.cookie.indexOf(";", start);
|
||||
if (end == -1) {
|
||||
end = document.cookie.length;
|
||||
by = unescape(document.cookie.substring(start, end));
|
||||
}
|
||||
}
|
||||
}
|
||||
setComparator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a comment div.
|
||||
*/
|
||||
function show(id) {
|
||||
$('#ao' + id).hide();
|
||||
$('#ah' + id).show();
|
||||
var context = $.extend({id: id}, opts);
|
||||
var popup = $(renderTemplate(popupTemplate, context)).hide();
|
||||
popup.find('textarea[name="proposal"]').hide();
|
||||
popup.find('a.by' + by).addClass('sel');
|
||||
var form = popup.find('#cf' + id);
|
||||
form.submit(function(event) {
|
||||
event.preventDefault();
|
||||
addComment(form);
|
||||
});
|
||||
$('#s' + id).after(popup);
|
||||
popup.slideDown('fast', function() {
|
||||
getComments(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a comment div.
|
||||
*/
|
||||
function hide(id) {
|
||||
$('#ah' + id).hide();
|
||||
$('#ao' + id).show();
|
||||
var div = $('#sc' + id);
|
||||
div.slideUp('fast', function() {
|
||||
div.remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an ajax request to get comments for a node
|
||||
* and insert the comments into the comments tree.
|
||||
*/
|
||||
function getComments(id) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: opts.getCommentsURL,
|
||||
data: {node: id},
|
||||
success: function(data, textStatus, request) {
|
||||
var ul = $('#cl' + id);
|
||||
var speed = 100;
|
||||
$('#cf' + id)
|
||||
.find('textarea[name="proposal"]')
|
||||
.data('source', data.source);
|
||||
|
||||
if (data.comments.length === 0) {
|
||||
ul.html('<li>No comments yet.</li>');
|
||||
ul.data('empty', true);
|
||||
} else {
|
||||
// If there are comments, sort them and put them in the list.
|
||||
var comments = sortComments(data.comments);
|
||||
speed = data.comments.length * 100;
|
||||
appendComments(comments, ul);
|
||||
ul.data('empty', false);
|
||||
}
|
||||
$('#cn' + id).slideUp(speed + 200);
|
||||
ul.slideDown(speed);
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem retrieving the comments.');
|
||||
},
|
||||
dataType: 'json'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment via ajax and insert the comment into the comment tree.
|
||||
*/
|
||||
function addComment(form) {
|
||||
var node_id = form.find('input[name="node"]').val();
|
||||
var parent_id = form.find('input[name="parent"]').val();
|
||||
var text = form.find('textarea[name="comment"]').val();
|
||||
var proposal = form.find('textarea[name="proposal"]').val();
|
||||
|
||||
if (text == '') {
|
||||
showError('Please enter a comment.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the form that is being submitted.
|
||||
form.find('textarea,input').attr('disabled', 'disabled');
|
||||
|
||||
// Send the comment to the server.
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: opts.addCommentURL,
|
||||
dataType: 'json',
|
||||
data: {
|
||||
node: node_id,
|
||||
parent: parent_id,
|
||||
text: text,
|
||||
proposal: proposal
|
||||
},
|
||||
success: function(data, textStatus, error) {
|
||||
// Reset the form.
|
||||
if (node_id) {
|
||||
hideProposeChange(node_id);
|
||||
}
|
||||
form.find('textarea')
|
||||
.val('')
|
||||
.add(form.find('input'))
|
||||
.removeAttr('disabled');
|
||||
var ul = $('#cl' + (node_id || parent_id));
|
||||
if (ul.data('empty')) {
|
||||
$(ul).empty();
|
||||
ul.data('empty', false);
|
||||
}
|
||||
insertComment(data.comment);
|
||||
var ao = $('#ao' + node_id);
|
||||
ao.find('img').attr({'src': opts.commentBrightImage});
|
||||
if (node_id) {
|
||||
// if this was a "root" comment, remove the commenting box
|
||||
// (the user can get it back by reopening the comment popup)
|
||||
$('#ca' + node_id).slideUp();
|
||||
}
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
form.find('textarea,input').removeAttr('disabled');
|
||||
showError('Oops, there was a problem adding the comment.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively append comments to the main comment list and children
|
||||
* lists, creating the comment tree.
|
||||
*/
|
||||
function appendComments(comments, ul) {
|
||||
$.each(comments, function() {
|
||||
var div = createCommentDiv(this);
|
||||
ul.append($(document.createElement('li')).html(div));
|
||||
appendComments(this.children, div.find('ul.comment-children'));
|
||||
// To avoid stagnating data, don't store the comments children in data.
|
||||
this.children = null;
|
||||
div.data('comment', this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After adding a new comment, it must be inserted in the correct
|
||||
* location in the comment tree.
|
||||
*/
|
||||
function insertComment(comment) {
|
||||
var div = createCommentDiv(comment);
|
||||
|
||||
// To avoid stagnating data, don't store the comments children in data.
|
||||
comment.children = null;
|
||||
div.data('comment', comment);
|
||||
|
||||
var ul = $('#cl' + (comment.node || comment.parent));
|
||||
var siblings = getChildren(ul);
|
||||
|
||||
var li = $(document.createElement('li'));
|
||||
li.hide();
|
||||
|
||||
// Determine where in the parents children list to insert this comment.
|
||||
for(var i=0; i < siblings.length; i++) {
|
||||
if (comp(comment, siblings[i]) <= 0) {
|
||||
$('#cd' + siblings[i].id)
|
||||
.parent()
|
||||
.before(li.html(div));
|
||||
li.slideDown('fast');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, this comment rates lower than all the others,
|
||||
// or it is the only comment in the list.
|
||||
ul.append(li.html(div));
|
||||
li.slideDown('fast');
|
||||
}
|
||||
|
||||
function acceptComment(id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: opts.acceptCommentURL,
|
||||
data: {id: id},
|
||||
success: function(data, textStatus, request) {
|
||||
$('#cm' + id).fadeOut('fast');
|
||||
$('#cd' + id).removeClass('moderate');
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem accepting the comment.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteComment(id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: opts.deleteCommentURL,
|
||||
data: {id: id},
|
||||
success: function(data, textStatus, request) {
|
||||
var div = $('#cd' + id);
|
||||
if (data == 'delete') {
|
||||
// Moderator mode: remove the comment and all children immediately
|
||||
div.slideUp('fast', function() {
|
||||
div.remove();
|
||||
});
|
||||
return;
|
||||
}
|
||||
// User mode: only mark the comment as deleted
|
||||
div
|
||||
.find('span.user-id:first')
|
||||
.text('[deleted]').end()
|
||||
.find('div.comment-text:first')
|
||||
.text('[deleted]').end()
|
||||
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
|
||||
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
|
||||
.remove();
|
||||
var comment = div.data('comment');
|
||||
comment.username = '[deleted]';
|
||||
comment.text = '[deleted]';
|
||||
div.data('comment', comment);
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem deleting the comment.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showProposal(id) {
|
||||
$('#sp' + id).hide();
|
||||
$('#hp' + id).show();
|
||||
$('#pr' + id).slideDown('fast');
|
||||
}
|
||||
|
||||
function hideProposal(id) {
|
||||
$('#hp' + id).hide();
|
||||
$('#sp' + id).show();
|
||||
$('#pr' + id).slideUp('fast');
|
||||
}
|
||||
|
||||
function showProposeChange(id) {
|
||||
$('#pc' + id).hide();
|
||||
$('#hc' + id).show();
|
||||
var textarea = $('#pt' + id);
|
||||
textarea.val(textarea.data('source'));
|
||||
$.fn.autogrow.resize(textarea[0]);
|
||||
textarea.slideDown('fast');
|
||||
}
|
||||
|
||||
function hideProposeChange(id) {
|
||||
$('#hc' + id).hide();
|
||||
$('#pc' + id).show();
|
||||
var textarea = $('#pt' + id);
|
||||
textarea.val('').removeAttr('disabled');
|
||||
textarea.slideUp('fast');
|
||||
}
|
||||
|
||||
function toggleCommentMarkupBox(id) {
|
||||
$('#mb' + id).toggle();
|
||||
}
|
||||
|
||||
/** Handle when the user clicks on a sort by link. */
|
||||
function handleReSort(link) {
|
||||
var classes = link.attr('class').split(/\s+/);
|
||||
for (var i=0; i<classes.length; i++) {
|
||||
if (classes[i] != 'sort-option') {
|
||||
by = classes[i].substring(2);
|
||||
}
|
||||
}
|
||||
setComparator();
|
||||
// Save/update the sortBy cookie.
|
||||
var expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 365);
|
||||
document.cookie= 'sortBy=' + escape(by) +
|
||||
';expires=' + expiration.toUTCString();
|
||||
$('ul.comment-ul').each(function(index, ul) {
|
||||
var comments = getChildren($(ul), true);
|
||||
comments = sortComments(comments);
|
||||
appendComments(comments, $(ul).empty());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to process a vote when a user clicks an arrow.
|
||||
*/
|
||||
function handleVote(link) {
|
||||
if (!opts.voting) {
|
||||
showError("You'll need to login to vote.");
|
||||
return;
|
||||
}
|
||||
|
||||
var id = link.attr('id');
|
||||
if (!id) {
|
||||
// Didn't click on one of the voting arrows.
|
||||
return;
|
||||
}
|
||||
// If it is an unvote, the new vote value is 0,
|
||||
// Otherwise it's 1 for an upvote, or -1 for a downvote.
|
||||
var value = 0;
|
||||
if (id.charAt(1) != 'u') {
|
||||
value = id.charAt(0) == 'u' ? 1 : -1;
|
||||
}
|
||||
// The data to be sent to the server.
|
||||
var d = {
|
||||
comment_id: id.substring(2),
|
||||
value: value
|
||||
};
|
||||
|
||||
// Swap the vote and unvote links.
|
||||
link.hide();
|
||||
$('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
|
||||
.show();
|
||||
|
||||
// The div the comment is displayed in.
|
||||
var div = $('div#cd' + d.comment_id);
|
||||
var data = div.data('comment');
|
||||
|
||||
// If this is not an unvote, and the other vote arrow has
|
||||
// already been pressed, unpress it.
|
||||
if ((d.value !== 0) && (data.vote === d.value * -1)) {
|
||||
$('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
|
||||
$('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
|
||||
}
|
||||
|
||||
// Update the comments rating in the local data.
|
||||
data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
|
||||
data.vote = d.value;
|
||||
div.data('comment', data);
|
||||
|
||||
// Change the rating text.
|
||||
div.find('.rating:first')
|
||||
.text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
|
||||
|
||||
// Send the vote information to the server.
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: opts.processVoteURL,
|
||||
data: d,
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem casting that vote.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a reply form used to reply to an existing comment.
|
||||
*/
|
||||
function openReply(id) {
|
||||
// Swap out the reply link for the hide link
|
||||
$('#rl' + id).hide();
|
||||
$('#cr' + id).show();
|
||||
|
||||
// Add the reply li to the children ul.
|
||||
var div = $(renderTemplate(replyTemplate, {id: id})).hide();
|
||||
$('#cl' + id)
|
||||
.prepend(div)
|
||||
// Setup the submit handler for the reply form.
|
||||
.find('#rf' + id)
|
||||
.submit(function(event) {
|
||||
event.preventDefault();
|
||||
addComment($('#rf' + id));
|
||||
closeReply(id);
|
||||
})
|
||||
.find('input[type=button]')
|
||||
.click(function() {
|
||||
closeReply(id);
|
||||
});
|
||||
div.slideDown('fast', function() {
|
||||
$('#rf' + id).find('textarea').focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the reply form opened with openReply.
|
||||
*/
|
||||
function closeReply(id) {
|
||||
// Remove the reply div from the DOM.
|
||||
$('#rd' + id).slideUp('fast', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
// Swap out the hide link for the reply link
|
||||
$('#cr' + id).hide();
|
||||
$('#rl' + id).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively sort a tree of comments using the comp comparator.
|
||||
*/
|
||||
function sortComments(comments) {
|
||||
comments.sort(comp);
|
||||
$.each(comments, function() {
|
||||
this.children = sortComments(this.children);
|
||||
});
|
||||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children comments from a ul. If recursive is true,
|
||||
* recursively include childrens' children.
|
||||
*/
|
||||
function getChildren(ul, recursive) {
|
||||
var children = [];
|
||||
ul.children().children("[id^='cd']")
|
||||
.each(function() {
|
||||
var comment = $(this).data('comment');
|
||||
if (recursive)
|
||||
comment.children = getChildren($(this).find('#cl' + comment.id), true);
|
||||
children.push(comment);
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
/** Create a div to display a comment in. */
|
||||
function createCommentDiv(comment) {
|
||||
if (!comment.displayed && !opts.moderator) {
|
||||
return $('<div class="moderate">Thank you! Your comment will show up '
|
||||
+ 'once it is has been approved by a moderator.</div>');
|
||||
}
|
||||
// Prettify the comment rating.
|
||||
comment.pretty_rating = comment.rating + ' point' +
|
||||
(comment.rating == 1 ? '' : 's');
|
||||
// Make a class (for displaying not yet moderated comments differently)
|
||||
comment.css_class = comment.displayed ? '' : ' moderate';
|
||||
// Create a div for this comment.
|
||||
var context = $.extend({}, opts, comment);
|
||||
var div = $(renderTemplate(commentTemplate, context));
|
||||
|
||||
// If the user has voted on this comment, highlight the correct arrow.
|
||||
if (comment.vote) {
|
||||
var direction = (comment.vote == 1) ? 'u' : 'd';
|
||||
div.find('#' + direction + 'v' + comment.id).hide();
|
||||
div.find('#' + direction + 'u' + comment.id).show();
|
||||
}
|
||||
|
||||
if (opts.moderator || comment.text != '[deleted]') {
|
||||
div.find('a.reply').show();
|
||||
if (comment.proposal_diff)
|
||||
div.find('#sp' + comment.id).show();
|
||||
if (opts.moderator && !comment.displayed)
|
||||
div.find('#cm' + comment.id).show();
|
||||
if (opts.moderator || (opts.username == comment.username))
|
||||
div.find('#dc' + comment.id).show();
|
||||
}
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple template renderer. Placeholders such as <%id%> are replaced
|
||||
* by context['id'] with items being escaped. Placeholders such as <#id#>
|
||||
* are not escaped.
|
||||
*/
|
||||
function renderTemplate(template, context) {
|
||||
var esc = $(document.createElement('div'));
|
||||
|
||||
function handle(ph, escape) {
|
||||
var cur = context;
|
||||
$.each(ph.split('.'), function() {
|
||||
cur = cur[this];
|
||||
});
|
||||
return escape ? esc.text(cur || "").html() : cur;
|
||||
}
|
||||
|
||||
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
|
||||
return handle(arguments[2], arguments[1] == '%' ? true : false);
|
||||
});
|
||||
}
|
||||
|
||||
/** Flash an error message briefly. */
|
||||
function showError(message) {
|
||||
$(document.createElement('div')).attr({'class': 'popup-error'})
|
||||
.append($(document.createElement('div'))
|
||||
.attr({'class': 'error-message'}).text(message))
|
||||
.appendTo('body')
|
||||
.fadeIn("slow")
|
||||
.delay(2000)
|
||||
.fadeOut("slow");
|
||||
}
|
||||
|
||||
/** Add a link the user uses to open the comments popup. */
|
||||
$.fn.comment = function() {
|
||||
return this.each(function() {
|
||||
var id = $(this).attr('id').substring(1);
|
||||
var count = COMMENT_METADATA[id];
|
||||
var title = count + ' comment' + (count == 1 ? '' : 's');
|
||||
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
|
||||
var addcls = count == 0 ? ' nocomment' : '';
|
||||
$(this)
|
||||
.append(
|
||||
$(document.createElement('a')).attr({
|
||||
href: '#',
|
||||
'class': 'sphinx-comment-open' + addcls,
|
||||
id: 'ao' + id
|
||||
})
|
||||
.append($(document.createElement('img')).attr({
|
||||
src: image,
|
||||
alt: 'comment',
|
||||
title: title
|
||||
}))
|
||||
.click(function(event) {
|
||||
event.preventDefault();
|
||||
show($(this).attr('id').substring(2));
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$(document.createElement('a')).attr({
|
||||
href: '#',
|
||||
'class': 'sphinx-comment-close hidden',
|
||||
id: 'ah' + id
|
||||
})
|
||||
.append($(document.createElement('img')).attr({
|
||||
src: opts.closeCommentImage,
|
||||
alt: 'close',
|
||||
title: 'close'
|
||||
}))
|
||||
.click(function(event) {
|
||||
event.preventDefault();
|
||||
hide($(this).attr('id').substring(2));
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
var opts = {
|
||||
processVoteURL: '/_process_vote',
|
||||
addCommentURL: '/_add_comment',
|
||||
getCommentsURL: '/_get_comments',
|
||||
acceptCommentURL: '/_accept_comment',
|
||||
deleteCommentURL: '/_delete_comment',
|
||||
commentImage: '/static/_static/comment.png',
|
||||
closeCommentImage: '/static/_static/comment-close.png',
|
||||
loadingImage: '/static/_static/ajax-loader.gif',
|
||||
commentBrightImage: '/static/_static/comment-bright.png',
|
||||
upArrow: '/static/_static/up.png',
|
||||
downArrow: '/static/_static/down.png',
|
||||
upArrowPressed: '/static/_static/up-pressed.png',
|
||||
downArrowPressed: '/static/_static/down-pressed.png',
|
||||
voting: false,
|
||||
moderator: false
|
||||
};
|
||||
|
||||
if (typeof COMMENT_OPTIONS != "undefined") {
|
||||
opts = jQuery.extend(opts, COMMENT_OPTIONS);
|
||||
}
|
||||
|
||||
var popupTemplate = '\
|
||||
<div class="sphinx-comments" id="sc<%id%>">\
|
||||
<p class="sort-options">\
|
||||
Sort by:\
|
||||
<a href="#" class="sort-option byrating">best rated</a>\
|
||||
<a href="#" class="sort-option byascage">newest</a>\
|
||||
<a href="#" class="sort-option byage">oldest</a>\
|
||||
</p>\
|
||||
<div class="comment-header">Comments</div>\
|
||||
<div class="comment-loading" id="cn<%id%>">\
|
||||
loading comments... <img src="<%loadingImage%>" alt="" /></div>\
|
||||
<ul id="cl<%id%>" class="comment-ul"></ul>\
|
||||
<div id="ca<%id%>">\
|
||||
<p class="add-a-comment">Add a comment\
|
||||
(<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
|
||||
<div class="comment-markup-box" id="mb<%id%>">\
|
||||
reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
|
||||
<code>``code``</code>, \
|
||||
code blocks: <code>::</code> and an indented block after blank line</div>\
|
||||
<form method="post" id="cf<%id%>" class="comment-form" action="">\
|
||||
<textarea name="comment" cols="80"></textarea>\
|
||||
<p class="propose-button">\
|
||||
<a href="#" id="pc<%id%>" class="show-propose-change">\
|
||||
Propose a change ▹\
|
||||
</a>\
|
||||
<a href="#" id="hc<%id%>" class="hide-propose-change">\
|
||||
Propose a change ▿\
|
||||
</a>\
|
||||
</p>\
|
||||
<textarea name="proposal" id="pt<%id%>" cols="80"\
|
||||
spellcheck="false"></textarea>\
|
||||
<input type="submit" value="Add comment" />\
|
||||
<input type="hidden" name="node" value="<%id%>" />\
|
||||
<input type="hidden" name="parent" value="" />\
|
||||
</form>\
|
||||
</div>\
|
||||
</div>';
|
||||
|
||||
var commentTemplate = '\
|
||||
<div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
|
||||
<div class="vote">\
|
||||
<div class="arrow">\
|
||||
<a href="#" id="uv<%id%>" class="vote" title="vote up">\
|
||||
<img src="<%upArrow%>" />\
|
||||
</a>\
|
||||
<a href="#" id="uu<%id%>" class="un vote" title="vote up">\
|
||||
<img src="<%upArrowPressed%>" />\
|
||||
</a>\
|
||||
</div>\
|
||||
<div class="arrow">\
|
||||
<a href="#" id="dv<%id%>" class="vote" title="vote down">\
|
||||
<img src="<%downArrow%>" id="da<%id%>" />\
|
||||
</a>\
|
||||
<a href="#" id="du<%id%>" class="un vote" title="vote down">\
|
||||
<img src="<%downArrowPressed%>" />\
|
||||
</a>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="comment-content">\
|
||||
<p class="tagline comment">\
|
||||
<span class="user-id"><%username%></span>\
|
||||
<span class="rating"><%pretty_rating%></span>\
|
||||
<span class="delta"><%time.delta%></span>\
|
||||
</p>\
|
||||
<div class="comment-text comment"><#text#></div>\
|
||||
<p class="comment-opts comment">\
|
||||
<a href="#" class="reply hidden" id="rl<%id%>">reply ▹</a>\
|
||||
<a href="#" class="close-reply" id="cr<%id%>">reply ▿</a>\
|
||||
<a href="#" id="sp<%id%>" class="show-proposal">proposal ▹</a>\
|
||||
<a href="#" id="hp<%id%>" class="hide-proposal">proposal ▿</a>\
|
||||
<a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
|
||||
<span id="cm<%id%>" class="moderation hidden">\
|
||||
<a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
|
||||
</span>\
|
||||
</p>\
|
||||
<pre class="proposal" id="pr<%id%>">\
|
||||
<#proposal_diff#>\
|
||||
</pre>\
|
||||
<ul class="comment-children" id="cl<%id%>"></ul>\
|
||||
</div>\
|
||||
<div class="clearleft"></div>\
|
||||
</div>\
|
||||
</div>';
|
||||
|
||||
var replyTemplate = '\
|
||||
<li>\
|
||||
<div class="reply-div" id="rd<%id%>">\
|
||||
<form id="rf<%id%>">\
|
||||
<textarea name="comment" cols="80"></textarea>\
|
||||
<input type="submit" value="Add reply" />\
|
||||
<input type="button" value="Cancel" />\
|
||||
<input type="hidden" name="parent" value="<%id%>" />\
|
||||
<input type="hidden" name="node" value="" />\
|
||||
</form>\
|
||||
</div>\
|
||||
</li>';
|
||||
|
||||
$(document).ready(function() {
|
||||
init();
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
$(document).ready(function() {
|
||||
// add comment anchors for all paragraphs that are commentable
|
||||
$('.sphinx-has-comment').comment();
|
||||
|
||||
// highlight search words in search results
|
||||
$("div.context").each(function() {
|
||||
var params = $.getQueryParameters();
|
||||
var terms = (params.q) ? params.q[0].split(/\s+/) : [];
|
||||
var result = $(this);
|
||||
$.each(terms, function() {
|
||||
result.highlightText(this.toLowerCase(), 'highlighted');
|
||||
});
|
||||
});
|
||||
|
||||
// directly open comment window if requested
|
||||
var anchor = document.location.hash;
|
||||
if (anchor.substring(0, 9) == '#comment-') {
|
||||
$('#ao' + anchor.substring(9)).click();
|
||||
document.location.hash = '#s' + anchor.substring(9);
|
||||
}
|
||||
});
|
||||