├── COPYING
├── README.md
├── doc
├── SoulseekProtocol – Museek+.html
├── SoulseekProtocol – Museek+_files
│ ├── babel.js
│ ├── folding.js
│ ├── jquery.js
│ ├── main.css
│ ├── museek_banner_github.png
│ ├── search.js
│ ├── trac.css
│ ├── trac.js
│ ├── trac_logo_mini.png
│ └── wiki.css
└── soulseek_protocol.html
├── index.js
├── lib
├── blacklist.json
├── build-list.js
├── check-port.js
├── client
│ ├── handlers.js
│ └── index.js
├── distrib-peer
│ ├── handlers.js
│ └── index.js
├── make-token.js
├── message
│ ├── decode-list.js
│ ├── decode-room.js
│ ├── emitter.js
│ ├── encode-list.js
│ ├── from-distrib.js
│ ├── from-peer.js
│ ├── from-server.js
│ ├── index.js
│ ├── to-distrib.js
│ ├── to-peer.js
│ └── to-server.js
├── nat-pmp-map.js
├── peer-server.js
├── peer
│ ├── handlers.js
│ └── index.js
├── search-share-list.js
├── soul-sock.js
├── transfer-peer
│ ├── handlers.js
│ └── index.js
└── upload-speed.js
├── livelook.svg
├── package.json
└── test.js
/README.md:
--------------------------------------------------------------------------------
1 | # livelook 👀
2 |
3 |
4 | a [soulseek](https://en.wikipedia.org/wiki/Soulseek) client written in
5 | javascript. soulseek allows users from around the world to connect to each other
6 | directly and share music (and other stuff).
7 |
8 | >Soulseek is an ad-free, spyware free, just plain free file sharing network for
9 | >Windows, Mac and Linux. Our rooms, search engine and search correlation system
10 | >make it easy for you to find people with similar interests, and make new
11 | >discoveries!
12 | >
13 | >-- [About Soulseek | Soulseek](https://www.slsknet.org/news/node/680)
14 |
15 | features supported:
16 | * [nat pmp](https://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol) and
17 | [upnp](https://en.wikipedia.org/wiki/Universal_Plug_and_Play) port-forwarding
18 | * chat rooms and private messages (including direct connections)
19 | * browsing user's share lists and vice versa
20 | * searching the network's files and responding to distributed searches
21 | * downloading and uploading with automatically updating share list
22 |
23 |
24 | i mainly tested this against nicotine-plus, but it works with soulseekqt too.
25 |
26 |
27 | ## example
28 | ```javascript
29 | const LiveLook = require('./');
30 | const fs = require('fs');
31 |
32 | let livelook = new LiveLook({
33 | username: 'toadtripler',
34 | password: 'not my password',
35 | sharedFolder: './mp3s',
36 | autojoin: [ 'nicotine' ]
37 | });
38 |
39 | livelook.on('error', console.error);
40 |
41 | livelook.login((err, res) => {
42 | if (err || !res.success) {
43 | return console.log('login failed');
44 | }
45 |
46 | livelook.on('sayChatroom', msg => {
47 | console.log(`[${msg.room}] <${msg.username}> ${msg.message}`);
48 | });
49 |
50 | livelook.on('messageUser', msg => {
51 | console.log(`<${msg.username}> ${msg.message}`);
52 | });
53 |
54 | livelook.search('hot dad web love', (err, res) => {
55 | if (err) {
56 | return console.error(err);
57 | }
58 |
59 | res = res.filter(item => item.slotsFree);
60 |
61 | if (!res) {
62 | console.log('no files found :(');
63 | return;
64 | }
65 |
66 | let downloaded = fs.createWriteStream('hot-dad-web-love.mp3');
67 | res = res.sort((a, b) => a.speed > b.speed ? -1: 0)[0];
68 | livelook.downloadFile(res.username, res.file).pipe(downloaded);
69 | downloaded.on('end', () => {
70 | livelook.messageUser(res.username, `hey thanks for ${res.file}!`);
71 | });
72 | });
73 | });
74 | ```
75 | ## install
76 |
77 | $ npm install --save livelook
78 |
79 | ## api
80 | ### livelook = new LiveLook(args)
81 | create a new `livelook` instance.
82 |
83 | ```javascript
84 | // args
85 | {
86 | username: '',
87 | password: '',
88 | server: 'server.slsknet.org',
89 | port: 2242, // port for server above, NOT the port we listen on
90 | waitPort: 2234, // port for peer server. will retry multiple options if fail
91 | sharedFolder: './mp3s',
92 | downloadFolder: './downloads',
93 | description: 'user biography',
94 | autojoin: [ 'chatrooms', 'joined', 'automatically' ],
95 | maxPeers: 100,
96 | uploadSlots: 2, // maximum uploads allowed at one time
97 | uploadThrottle: 56 * 1024, // speed to throttle uploads in bytes
98 | downloadThrottle: 56 * 1024
99 | }
100 | ```
101 |
102 | ### livelook.init([done])
103 | initialize our share list and connect to the soulseek server. you don't need to
104 | call this if you just use login below.
105 |
106 | ### livelook.login([username, password, done])
107 | login to the soulseek server, and initialize our peer server if it isn't
108 | already. username and password are optional if the instance has them.
109 |
110 | ### livelook.refreshShareList([done])
111 | re-scan `livelook.sharedFolder` and repopulate `livelook.shareList`. this is
112 | what other users see when they browse us, or when we respond to searches.
113 |
114 | ### livelook.sayChatroom(room, message)
115 | send a message to a chatroom.
116 |
117 | ### livelook.leaveChatroom(room)
118 | leave a chatroom and stop receiving messages from it.
119 |
120 | ### livelook.joinRoom(room)
121 | join a chatroom and start accepting messages from it.
122 |
123 | ### livelook.messageUser(username, message)
124 | send a private message to a specific user.
125 |
126 | ### livelook.setStatus(status)
127 | set our online/away status.
128 |
129 | `status` can be a `Number` (1 for away, 2 for online), `'away'` or `'online'`.
130 |
131 | ### livelook.refreshUploadSpeed([done])
132 | re-calculate our upload speed from [speedtest.net](https://www.speedtest.net/).
133 |
134 | ### livelook.getPeerAddress(username, done)
135 | get a peer's ip address and port based on their username.
136 |
137 | ### livelook.getPeerByUsername(username, done)
138 | get a peer instance based on a username. this will first check our pre-existing
139 | peers, then it tries to make a direct connection to the peer until finally
140 | requesting the server connect the peer to us.
141 |
142 | ### livelook.getShareFileList(username, done)
143 | get all the files a user is sharing. may take a while as some people share
144 | large amounts of files, and the message must be decompressed.
145 |
146 | ### livelook.searchUserShares(username, query, done)
147 | search a user's shares for a query. nicotine users max out at 50 by default.
148 |
149 | ### livelook.getUserInfo(username, done)
150 | get a user's description (biography), picture (avatar) as a buffer,
151 | upload slots, queue size and slots free.
152 |
153 | ### livelook.getFolderContents(username, folder, done)
154 | get a list of all the files in the specified folder.
155 |
156 | ### livelook.downloadFile(username, file, [fileStart = 0])
157 | download a file from a user. this returns a `ReadableStream`, and will also
158 | emit a `queue` event with its position if we can't download it immediately. pass
159 | in `fileStart` to indicate where to begin downloading the file in bytes (to
160 | resume interrupted downloads).
161 |
162 | ### livelook.searchFiles(query, [args = { timeout, max }, done])
163 | search other peers for files. this returns a `ReadableStream` in `objectMode`.
164 | on search results it will emit `data` events containing the following:
165 |
166 | ```javascript
167 | { username, file, size, bitrate, vbr, duration, slotsFree, speed, queueSize }
168 | ```
169 |
170 | if `done` is provided, the terms will be concatted and passed in either after
171 | the timeout finishes or the max results are reached.
172 |
173 | ## how it works
174 | soulseek is largely peer-to-peer, but still relies on a central server for
175 | chat rooms, messaging, piercing firewalls and finding peers.
176 |
177 | ### initializing
178 | first we set up a local server to accept peer connections (and port forward with
179 | nat pmp or upnp if possible). then we connect to slsknet.org (or any other
180 | soulseek server) as a separate client and login. we can now begin to chat and
181 | browse.
182 |
183 | ### finding peers
184 | we can connect to peers by fetching their ip and port based on their username
185 | from the soulseek server, but if they aren't port-forwarded this will fail. the
186 | next step is to send a connection request via the soulseek server to tell them
187 | to connect to us. if this fails, there is no way for them to connect to us. this
188 | is why it's a good idea to enable nat pmp or port forward manually.
189 |
190 | ### searching
191 | after logging in, we tell the server we're orphaned and have no parents. after
192 | an arbitrary amount of time (usually around 30 seconds), the server gives a list
193 | of potential parents. once we connect to one successfully, we can also become a
194 | parent. our parent will contionously send us search requests from other peers.
195 | we can respond to them directly if we have the results, but also send the
196 | request to all of our children so they can do the same.
197 |
198 | ## see also
199 | * [Soulseek.NET](https://github.com/jpdillingham/Soulseek.NET) by @jpdillingham
200 | * [museek-plus](https://github.com/eLvErDe/museek-plus) by @eLvErDe
201 | * [protocol docs](https://htmlpreview.github.io/?http://github.com/misterhat/livelook/blob/master/doc/SoulseekProtocol%20%E2%80%93%20Museek%2B.html)
202 | * [nicotine-plus](https://github.com/Nicotine-Plus/nicotine-plus)
203 | * [slsk-client](https://github.com/f-hj/slsk-client) by @f-hj.
204 | * [soleseek protocol docs](https://htmlpreview.github.io/?https://github.com/misterhat/livelook/blob/master/doc/soulseek_protocol.html)
205 |
206 | ## donate
207 | [donate to keep the central server alive!](https://www.slsknet.org/donate.php)
208 |
209 | ## license
210 | Copyright (C) 2019 Zorian Medwid
211 |
212 | This program is free software: you can redistribute it and/or modify
213 | it under the terms of the GNU Affero General Public License as
214 | published by the Free Software Foundation, either version 3 of the
215 | License, or (at your option) any later version.
216 |
217 | This program is distributed in the hope that it will be useful,
218 | but WITHOUT ANY WARRANTY; without even the implied warranty of
219 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
220 | GNU Affero General Public License for more details.
221 |
222 | You should have received a copy of the GNU Affero General Public License
223 | along with this program. If not, see http://www.gnu.org/licenses/.
224 |
225 | **You may not distribute this program without offering the source code. Hosting
226 | a web service that utilizes livelook is distrubtion.**
227 |
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Babel JavaScript Support
3 | *
4 | * Copyright (C) 2008 Edgewall Software
5 | * All rights reserved.
6 | *
7 | * This software is licensed as described in the file COPYING, which
8 | * you should have received as part of this distribution. The terms
9 | * are also available at http://babel.edgewall.org/wiki/License.
10 | *
11 | * This software consists of voluntary contributions made by many
12 | * individuals. For the exact contribution history, see the revision
13 | * history and logs, available at http://babel.edgewall.org/log/.
14 | */
15 |
16 | /**
17 | * A simple module that provides a gettext like translation interface.
18 | * The catalog passed to load() must be a object conforming to this
19 | * interface::
20 | *
21 | * {
22 | * messages: an object of {msgid: translations} items where
23 | * translations is an array of messages or a single
24 | * string if the message is not pluralizable.
25 | * plural_expr: the plural expression for the language.
26 | * locale: the identifier for this locale.
27 | * domain: the name of the domain.
28 | * }
29 | *
30 | * Missing elements in the object are ignored.
31 | *
32 | * Typical usage::
33 | *
34 | * var translations = babel.Translations.load(...).install();
35 | */
36 | var babel = new function() {
37 |
38 | var defaultPluralExpr = function(n) { return n == 1 ? 0 : 1; };
39 | var formatRegex = /%(?:(?:\(([^\)]+)\))?([disr])|%)/g;
40 | var translations = {};
41 | var merged;
42 |
43 | /**
44 | * A translations object implementing the gettext interface
45 | */
46 | var Translations = this.Translations = function(locale, domain) {
47 | this.messages = {};
48 | this.locale = locale || 'unknown';
49 | this.domain = domain || 'messages';
50 | this.pluralexpr = defaultPluralExpr;
51 | };
52 |
53 | /**
54 | * Create a new translations object from the catalog and return it.
55 | * See the babel-module comment for more details.
56 | */
57 | Translations.load = function(catalog) {
58 | var rv = new Translations();
59 | rv.load(catalog);
60 | translations[rv.domain] = rv;
61 | merged.load(catalog);
62 | return rv;
63 | };
64 |
65 | /**
66 | * Get a Translations instance from the loaded translations. If the
67 | * specified domain doesn't exist, returns a dummy Translations
68 | * instance.
69 | */
70 | Translations.get = function(domain) {
71 | return translations[domain] || (new Translations({domain: domain}));
72 | };
73 |
74 | Translations.prototype = {
75 | /**
76 | * translate a single string
77 | *
78 | * If extra parameters are given, use them to fill the format
79 | * specified by the string.
80 | */
81 | gettext: function(string) {
82 | var translated = this.messages[string];
83 | if (typeof translated == 'undefined')
84 | translated = string;
85 | else if (typeof translated != 'string')
86 | translated = translated[0];
87 | if (arguments.length > 1) {
88 | arguments[0] = translated;
89 | return babel.format.apply(this, arguments);
90 | }
91 | return translated;
92 | },
93 |
94 | /**
95 | * translate a pluralizable string
96 | *
97 | * If extra parameters are given, use them to fill the format
98 | * specified by the string.
99 | */
100 | ngettext: function(singular, plural, n) {
101 | var translated = this.messages[singular];
102 | if (typeof translated == 'undefined')
103 | translated = (n == 1) ? singular : plural;
104 | else
105 | translated = translated[this.pluralexpr(n)];
106 | if (arguments.length > 3) {
107 | var format_args = Array.prototype.slice.call(arguments, 3);
108 | format_args.unshift(translated);
109 | return babel.format.apply(this, format_args)
110 | }
111 | return translated;
112 | },
113 |
114 | /**
115 | * Install this translation document wide. After this call, there are
116 | * three new methods on the window object: _, gettext and ngettext
117 | */
118 | install: function() {
119 | window._ = window.gettext = function() {
120 | return merged.gettext.apply(merged, arguments);
121 | };
122 | window.ngettext = function(singular, plural, n) {
123 | return merged.ngettext.apply(merged, arguments);
124 | };
125 | return this;
126 | },
127 |
128 | /**
129 | * Works like Translations.load but updates the instance rather
130 | * then creating a new one.
131 | */
132 | load: function(catalog) {
133 | if (catalog.messages)
134 | this.update(catalog.messages)
135 | if (catalog.plural_expr)
136 | this.setPluralExpr(catalog.plural_expr);
137 | if (catalog.locale)
138 | this.locale = catalog.locale;
139 | if (catalog.domain)
140 | this.domain = catalog.domain;
141 | return this;
142 | },
143 |
144 | /**
145 | * Updates the translations with the object of messages.
146 | */
147 | update: function(mapping) {
148 | for (var key in mapping)
149 | if (mapping.hasOwnProperty(key))
150 | this.messages[key] = mapping[key];
151 | return this;
152 | },
153 |
154 | /**
155 | * Sets the plural expression
156 | */
157 | setPluralExpr: function(expr) {
158 | this.pluralexpr = new Function('n', 'return +(' + expr + ')');
159 | return this;
160 | }
161 | };
162 |
163 | merged = new Translations({});
164 |
165 | /**
166 | * Translate a single string in the specified domain.
167 | *
168 | * If extra parameters are given, use them to fill the format
169 | * specified by the string.
170 | */
171 | window.dgettext = this.dgettext = function(domain, string) {
172 | var rv = translations[domain];
173 | var args = Array.prototype.slice.call(arguments, 1);
174 | if (typeof rv != 'undefined')
175 | return rv.gettext.apply(rv, args);
176 | if (arguments.length > 1)
177 | return babel.format.apply(this, args);
178 | return string;
179 | };
180 |
181 | /**
182 | * Translate a pluralizable string in the specified domain.
183 | *
184 | * If extra parameters are given, use them to fill the format
185 | * specified by the string.
186 | */
187 | window.dngettext = this.dngettext = function(domain, singular, plural, n) {
188 | var rv = translations[domain];
189 | if (typeof rv != 'undefined') {
190 | var args = Array.prototype.slice.call(arguments, 1);
191 | return rv.ngettext.apply(rv, args);
192 | }
193 | if (arguments.length > 4) {
194 | var args = Array.prototype.slice.call(arguments, 4);
195 | args.unshift(singular);
196 | return babel.format.apply(this, format_args)
197 | }
198 | return (n == 1) ? singular : plural;
199 | };
200 |
201 | /**
202 | * A python inspired string formatting function. Supports named and
203 | * positional placeholders and "s", "d" and "i" as type characters
204 | * without any formatting specifications.
205 | *
206 | * Examples::
207 | *
208 | * babel.format(_('Hello %s'), name)
209 | * babel.format(_('Progress: %(percent)s%%'), {percent: 100})
210 | */
211 | this.format = function() {
212 | var arg, string = arguments[0], idx = 0;
213 | if (arguments.length == 1)
214 | return string;
215 | else if (arguments.length == 2 && typeof arguments[1] == 'object')
216 | arg = arguments[1];
217 | else {
218 | arg = [];
219 | for (var i = 1, n = arguments.length; i != n; ++i)
220 | arg[i - 1] = arguments[i];
221 | }
222 | return string.replace(formatRegex, function(all, name, type) {
223 | if (all == '%%') return '%';
224 | var value = arg[name || idx++];
225 | return (type == 'i' || type == 'd') ? +value : value;
226 | });
227 | }
228 |
229 | };
230 |
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/folding.js:
--------------------------------------------------------------------------------
1 | (function($){
2 |
3 | $.fn.enableFolding = function(autofold, snap) {
4 | var fragId = document.location.hash;
5 | if (fragId && /^#no\d+$/.test(fragId))
6 | fragId = parseInt(fragId.substr(3));
7 | if (snap == undefined)
8 | snap = false;
9 |
10 | var count = 1;
11 | return this.each(function() {
12 | // Use first child as a trigger, or generate a trigger from the text
13 | var trigger = $(this).children("a").eq(0);
14 | if (trigger.length == 0) {
15 | trigger = $("");
17 | trigger.html($(this).html());
18 | $(this).text("");
19 | $(this).append(trigger);
20 | }
21 |
22 | trigger.click(function() {
23 | var div = $(this.parentNode.parentNode).toggleClass("collapsed");
24 | return snap && !div.hasClass("collapsed");
25 | });
26 | if (autofold && (count != fragId))
27 | trigger.parents().eq(1).addClass("collapsed");
28 | count++;
29 | });
30 | }
31 |
32 | /** Enable columns of a table to be hidden by clicking on the column header.
33 | *
34 | * +------------------+------+---- ... ---+---------------------+
35 | * |column_headers[0] | ... | | column_headers[k-1] | <- c_h_row
36 | * +==================+======+==== ... ===+=====================+
37 | * | row_headers[0] | row_headers[1] | row_headers[1*k-1] | <- rows[0]
38 | * | row_headers[k] | row_headers[k+1] | row_headers[2*k-1] | <- rows[1]
39 | * ...
40 | */
41 | $.fn.enableCollapsibleColumns = function(recovery_area) {
42 | // column headers
43 | var c_h_row = $('thead tr', this);
44 | var column_headers = $('th', c_h_row).not(recovery_area);
45 | var k = column_headers.length;
46 | // row headers
47 | var tbody = $('tbody', this);
48 | var row_headers = $('th', tbody);
49 | var rows = $('tr', tbody);
50 | var n = row_headers.length / k;
51 |
52 | // add a 'hide' callback to each column header
53 | column_headers.each(function(j) {
54 | function hide() {
55 | // remove and save column j
56 | var th = $(this);
57 | th.css('display', 'none');
58 | for ( var i = 0; i < n; i++ )
59 | row_headers.eq(i*k+j).css('display', 'none');
60 | // create a recovery button and its "show" callback
61 | recovery_area.prepend($("").addClass("recover")
62 | .text(_("Show %(title)s", {title: th.text()}))
63 | .click(function() {
64 | $(this).remove();
65 | th.show();
66 | for (var i = 0; i < n; i++)
67 | row_headers.eq(i*k+j).css('display', 'table-cell');
68 | })
69 | );
70 | };
71 | $(this).click(hide)
72 | .css('cursor', 'pointer')
73 | .attr('title', _("%(title)s (click to hide column)",
74 | {title: $(this).attr('title')}));
75 | });
76 | }
77 |
78 | })(jQuery);
79 |
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/main.css:
--------------------------------------------------------------------------------
1 | /* Link styles */
2 | a:hover, a:link, a:active, a:visited {
3 | color: #5785ed;
4 | }
5 | /* Link styles */
6 |
7 |
8 | /* Styles for the roadmap view */
9 | .milestone .info h2 em {
10 | color: #57c1ed;
11 | }
12 | /* Styles for the roadmap view */
13 |
14 |
15 | /* Timeline */
16 | dt em {
17 | color: #5785ed;
18 | }
19 | /* Timeline */
20 |
21 |
22 | /* Browser */
23 | h1 :link, h1 :visited {
24 | color: #57c1ed;
25 | }
26 | /* Browser */
27 |
28 |
29 | /* Forms */
30 | input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
31 | background: #57c1ed;
32 | }
33 | /* Forms */
34 |
35 |
36 | /* Left menu */
37 | #menu_left {
38 | width: 135px;
39 | position: absolute;
40 | float: left;
41 | top: 126px;
42 | margin: 0px;
43 | padding: 50px 10px 50px 10px;
44 | color: #57c1ed;
45 | border: black 1px solid;
46 | border-top: none;
47 | background-color: #f7f7f7;
48 | z-index: 900;
49 | }
50 | #menu_left li {
51 | font-weight: bold;
52 | }
53 | #menu_left a:link, a:visited {
54 | color: #5785ed;
55 | }
56 | #menu_left ul li ul li {
57 | font-weight: normal;
58 | }
59 | #menu_left ul {
60 | list-style: none;
61 | margin: 0;
62 | padding: 0;
63 | }
64 | #menu_left ul li {
65 | margin: 0;
66 | padding: 4px;
67 | }
68 | /* Left menu */
69 |
70 |
71 | /* Banner */
72 | #header {
73 | padding: 0px;
74 | margin: 10px 0px 0px 10px;
75 | }
76 | /* Banner */
77 |
78 |
79 | /* Search box */
80 | #search {
81 | position: absolute;
82 | top: 40px;
83 | right: 15px;
84 | margin: 0px;
85 | padding: 0px;
86 | }
87 | /* Search box */
88 |
89 |
90 | /* Meta nav */
91 | #metanav {
92 | position: absolute;
93 | top: 70px;
94 | right: 10px;
95 | margin: 0px;
96 | padding: 0px;
97 | }
98 | /* Meta nav */
99 |
100 |
101 | /* Main nav */
102 | #mainnav {
103 | background: #f7f7f7;
104 | position: absolute;
105 | top: 100px;
106 | right: 2%px;
107 | width: 98%;
108 | margin: 0px;
109 | height: 25px;
110 | padding: 0px;
111 | }
112 | #mainnav ul {
113 | display: table;
114 | float: right;
115 | margin: 0px;
116 | padding: 0px;
117 | }
118 | #mainnav li {
119 | display:table-cell;
120 | margin: 0px;
121 | padding: 0px;
122 | }
123 | #mainnav ul li a {
124 | position: relative;
125 | float: right;
126 | height: 21px;
127 | padding: 0px;
128 | margin: 0px;
129 | }
130 | #mainnav .active :link, #mainnav .active :visited {
131 | background: #57c1ed;
132 | }
133 | /* Main nav */
134 |
135 |
136 | /* Ctx nav */
137 | #ctxtnav {
138 | margin-top: 7.0em;
139 | }
140 | /* Ctx nav */
141 |
142 |
143 | /* Main page */
144 | *>#content {
145 | padding: 0;
146 | margin: 15px 15px 0px 180px;
147 | text-align: left;
148 | min-height: 770px;
149 | }
150 | * html #content {
151 | padding: 0;
152 | margin: 15px 15px 0px 180px;
153 | text-align: left;
154 | height: 770px;
155 | }
156 | /* Main page */
157 |
158 |
159 | /* Need by Doxygen plugin */
160 | .tabs{
161 | margin: 85px 0px 0px 0px;
162 | }
163 | /* Need by Doxygen plugin */
164 |
165 |
166 | /* NewsFlash Plugin */
167 | div.newsflash {
168 | color: black;
169 | background: #f7f7f7;
170 | border: solid 2px #43eeff;
171 | }
172 | /* NewsFlash Plugin */
173 |
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/museek_banner_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/misterhat/livelook/630241218e6dbd6db19779bbf6e95c1ffe6a8b38/doc/SoulseekProtocol – Museek+_files/museek_banner_github.png
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/search.js:
--------------------------------------------------------------------------------
1 | (function($){
2 |
3 | /* Adapted from http://www.kryogenix.org/code/browser/searchhi/ */
4 | $.fn.highlightText = function(text, className, caseSensitive) {
5 | function highlight(node) {
6 | if (node.nodeType == 3) { // Node.TEXT_NODE
7 | var val = node.nodeValue;
8 | var pos = (caseSensitive ? val : val.toLowerCase()).indexOf(text);
9 | if (pos >= 0 && !$(node.parentNode).hasClass(className)) {
10 | var span = document.createElement("span");
11 | span.className = className;
12 | var txt = document.createTextNode(val.substr(pos, text.length));
13 | span.appendChild(txt);
14 | node.parentNode.insertBefore(span, node.parentNode.insertBefore(
15 | document.createTextNode(val.substr(pos + text.length)),
16 | node.nextSibling));
17 | node.nodeValue = val.substr(0, pos);
18 | }
19 | } else if (!$(node).is("button, select, textarea")) {
20 | $.each(node.childNodes, function() { highlight(this) });
21 | }
22 | }
23 | return this.each(function() { highlight(this) });
24 | }
25 |
26 | $(document).ready(function() {
27 | $("p.filters").on("click", ":checkbox, :checkbox + label",
28 | function(event) {
29 | if (!event.metaKey && !event.altKey)
30 | return;
31 | var clicked = this.tagName === "LABEL" ?
32 | document.getElementById(this.htmlFor) : this;
33 | var $clicked = $(clicked);
34 | $clicked.prop("checked", true);
35 | $clicked.siblings(":checkbox").prop("checked", false);
36 | if (this.tagName === "LABEL") {
37 | return false;
38 | }
39 | });
40 |
41 | var elems = $(".searchable");
42 | if (!elems.length) return;
43 |
44 | function getSearchTerms(url) {
45 | if (url.indexOf("?") == -1) return [];
46 | var params = url.substr(url.indexOf("?") + 1).split("&");
47 | for (var p in params) {
48 | var param = params[p].split("=");
49 | if (param.length < 2) continue;
50 | if (param[0] == "q" || param[0] == "p") {// q= for Google, p= for Yahoo
51 | var query = decodeURIComponent(param[1].replace(/\+/g, " "));
52 | if (query[0] == "!") query = query.slice(1);
53 | var terms = [];
54 | $.each(query.split(/(".*?"|'.*?'|\s+)/), function() {
55 | if (terms.length < 10) {
56 | term = this.replace(/^\s+$/, "")
57 | .replace(/^['"]/, "")
58 | .replace(/['"]$/, "");
59 | if (term.length >= 3)
60 | terms.push(term);
61 | }
62 | });
63 | return terms;
64 | }
65 | }
66 | return [];
67 | }
68 |
69 | var terms = getSearchTerms(document.URL);
70 | if (!terms.length) terms = getSearchTerms(document.referrer);
71 | if (terms.length) {
72 | $.each(terms, function(idx) {
73 | elems.highlightText(this.toLowerCase(), "searchword" + (idx % 5));
74 | });
75 | } else {
76 | function scrollToHashSearchMatch() {
77 | var h = window.location.hash;
78 | var direction = h[1];
79 | var case_insensitive = h.match(/\/i$/);
80 | if (direction == '/' || direction == '?') {
81 | var hterm = h.substr(2);
82 | if (case_insensitive)
83 | hterm = hterm.substr(0, hterm.length - 2).toLowerCase();
84 | $('.searchword0').each(function() {
85 | $(this).after($(this).html()).remove();
86 | });
87 | elems.highlightText(hterm, "searchword0", !case_insensitive);
88 | var hmatches = $('.searchword0');
89 | if (direction == '?')
90 | hmatches = hmatches.last();
91 | hmatches.first().each(function() {
92 | var offset = $(this).offset().top;
93 | window.scrollTo(0, offset);
94 | });
95 | }
96 | }
97 | window.onhashchange = scrollToHashSearchMatch;
98 | scrollToHashSearchMatch();
99 | }
100 | });
101 |
102 | })(jQuery);
103 |
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/trac.css:
--------------------------------------------------------------------------------
1 | /* -*- coding: utf-8 -*- */
2 | html { overflow-y: scroll }
3 | body {
4 | background: #fff;
5 | color: #000;
6 | margin: 10px;
7 | padding: 0;
8 | }
9 | body, th, tr {
10 | font: normal 13px Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif;
11 | }
12 | h1, h2, h3, h4 {
13 | font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif;
14 | font-weight: bold;
15 | letter-spacing: -0.018em;
16 | page-break-after: avoid;
17 | }
18 | h1 { font-size: 19px; margin: .15em 1em 0.5em 0 }
19 | h2 { font-size: 16px }
20 | h3 { font-size: 14px }
21 | hr { border: none; border-top: 1px solid #ccb; margin: 2em 0 }
22 | address { font-style: normal }
23 | img { border: none }
24 |
25 | .underline { text-decoration: underline }
26 | ol.loweralpha { list-style-type: lower-alpha }
27 | ol.upperalpha { list-style-type: upper-alpha }
28 | ol.lowerroman { list-style-type: lower-roman }
29 | ol.upperroman { list-style-type: upper-roman }
30 | ol.arabic { list-style-type: decimal }
31 |
32 | /* Link styles */
33 | :link, :visited {
34 | text-decoration: none;
35 | color: #b00;
36 | border-bottom: 1px dotted #bbb;
37 | }
38 | :link:hover, :visited:hover { background-color: #eee; color: #555 }
39 | h1 :link, h1 :visited ,h2 :link, h2 :visited, h3 :link, h3 :visited,
40 | h4 :link, h4 :visited, h5 :link, h5 :visited, h6 :link, h6 :visited {
41 | color: inherit;
42 | }
43 |
44 | /* Heading anchors */
45 | .anchor:link, .anchor:visited {
46 | border: none;
47 | color: #d7d7d7;
48 | font-size: .8em;
49 | vertical-align: text-top;
50 | }
51 | * > .anchor:link, * > .anchor:visited {
52 | visibility: hidden;
53 | }
54 | h1:hover .anchor, h2:hover .anchor, h3:hover .anchor,
55 | h4:hover .anchor, h5:hover .anchor, h6:hover .anchor,
56 | span:hover .anchor {
57 | visibility: visible;
58 | }
59 |
60 | h1:target, h2:target, h3:target, h4:target, h5:target, h6:target,
61 | span:target {
62 | background: #ffb;
63 | box-shadow: .1em .1em .4em .1em #DDA;
64 | border-radius: .2em;
65 | }
66 |
67 | @media screen {
68 | a.ext-link .icon {
69 | background: url(../extlink.gif) left center no-repeat;
70 | padding-left: 15px;
71 | }
72 | a.mail-link .icon {
73 | background: url(../envelope.png) left center no-repeat;
74 | padding-left: 16px;
75 | }
76 | a.trac-rawlink, a.trac-ziplink {
77 | background: url('../download.png') right center no-repeat;
78 | padding-right: 16px;
79 | border-bottom: none;
80 | }
81 | }
82 |
83 | /* Forms */
84 | input, textarea, select, .trac-button { margin: 2px }
85 | /* Avoid respect to system font settings for controls on Firefox, #11607 */
86 | input, select, button {
87 | font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif;
88 | font-size: 100%;
89 | }
90 | /* Avoid to inherit white-space of its parent element for IE 11, #11376 */
91 | textarea { white-space: pre-wrap }
92 | input, select, .trac-button { vertical-align: middle }
93 | input[type=button], input[type=submit], input[type=reset], button {
94 | *overflow: visible; /* Workaround too much margin on button in IE7 */
95 | }
96 | input[type=button], input[type=submit], input[type=reset], .trac-button {
97 | background: #eee;
98 | color: #222;
99 | border: 1px outset #eee;
100 | border-radius: .3em;
101 | box-shadow: .1em .1em .4em 0 #888;
102 | padding: .1em .5em .2em;
103 | text-shadow: .04em .04em #ddd;
104 | cursor: pointer;
105 | }
106 | input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover,
107 | .trac-button:hover {
108 | background: #f6f6f6;
109 | box-shadow: .1em .1em .6em 0 #999;
110 | text-shadow: .04em .04em #fcfcfc;
111 | }
112 | input[type=button]:active, input[type=submit]:active, input[type=reset]:active,
113 | .trac-button:active, .inlinebuttons a:active {
114 | position: relative;
115 | top: .1em;
116 | left: .1em;
117 | }
118 | input[type=button][disabled], input[type=submit][disabled],
119 | input[type=reset][disabled], .trac-button[disabled] {
120 | background: #f6f6f6;
121 | border-style: solid;
122 | color: #999;
123 | }
124 | input[type=text], input.textwidget, textarea {
125 | border: 1px solid #d7d7d7;
126 | border-radius: .3em;
127 | }
128 | input[type=text], input.textwidget { padding: .25em .5em }
129 | input[type=text]:focus, input.textwidget:focus, textarea:focus {
130 | border: 1px solid #886;
131 | }
132 | option { border-bottom: 1px dotted #d7d7d7 }
133 | fieldset {
134 | border: 1px solid #d7d7d7;
135 | border-radius: .4em;
136 | margin: 1em 0;
137 | background: #F7F7F0;
138 | box-shadow: .1em .1em 1em 0 #E7E7E7 inset;
139 | padding: 1em;
140 | }
141 | p.hint, span.hint, span.trac-datetimehint {
142 | color: #666;
143 | font-size: 85%;
144 | font-style: italic;
145 | margin: .5em 0;
146 | padding-left: 1em;
147 | }
148 | span.hint { vertical-align: middle; }
149 | p.help { color: #666; font-size: 90%; margin: 1em .5em .5em; }
150 | legend {
151 | color: #999;
152 | margin-left: .6em;
153 | padding: 0 .25em;
154 | font-size: 90%;
155 | font-weight: bold;
156 | }
157 | label.disabled, .disabled span.hint, .disabled p.help, .disabled legend,
158 | .disabled input, .disabled select, .readonly span.hint, .readonly p.help {
159 | color: #d7d7d7
160 | }
161 | .buttons { margin: .5em .5em .5em 0 }
162 | .buttons form, .buttons form div { display: inline }
163 | .buttons input { margin: 1em .5em .1em 0 }
164 |
165 | .inlinebuttons input,
166 | .inlinebuttons .trac-button,
167 | .inlinebuttons a {
168 | border: 1px solid #ddd;
169 | border-radius: 1em;
170 | height: 1.6em;
171 | padding: 0 .4em .1em;
172 | font-size: 70%;
173 | box-shadow: none;
174 | margin: 0 .1em .1em;
175 | background: none;
176 | color: #808080;
177 | cursor: pointer;
178 | }
179 | .uisymbols .inlinebuttons input[type=button],
180 | .uisymbols .inlinebuttons input[type=submit],
181 | .uisymbols .inlinebuttons .trac-button,
182 | .uisymbols .inlinebuttons a {
183 | font-size: 100%;
184 | }
185 | .inlinebuttons input[type=button]:hover,
186 | .inlinebuttons input[type=submit]:hover,
187 | .inlinebuttons .trac-button:hover,
188 | .inlinebuttons a:hover {
189 | background: #f6f6f6;
190 | color: #333;
191 | text-shadow: .04em .04em #fcfcfc;
192 | box-shadow: .1em .1em .6em 0 #999;
193 | }
194 | input[type=button].trac-delete,
195 | input[type=submit].trac-delete,
196 | .trac-button.trac-delete {
197 | color: #d31313;
198 | }
199 | input[type=button].trac-delete:hover,
200 | input[type=submit].trac-delete:hover,
201 | .trac-button.trac-delete:hover {
202 | color: #e31313;
203 | }
204 | textarea.trac-fullwidth, input.trac-fullwidth, button.trac-fullwidth {
205 | -moz-box-sizing: border-box;
206 | box-sizing: border-box;
207 | margin-left: 0;
208 | margin-right: 0;
209 | width: 100%;
210 | }
211 | textarea.trac-fullwidth { padding: 2px; }
212 | /* Resource edit forms (detailed view) */
213 | form.mod { margin: 1em 0 }
214 | form.mod .field { margin: .5em 0; }
215 | form.mod .field.description { margin-top: 1em; }
216 | form.mod .field label { padding-left: .2em }
217 | form.mod #changeinfo .field { margin: 0.25em 0 0.75em 0 }
218 | form.mod #changeinfo .options { padding: 0 0 0.5em 1em }
219 |
220 | /* Header */
221 | #header hr { display: none }
222 | #header h1 { margin: 1.5em 0 -1.5em; padding: 0 }
223 | #header img { border: none; margin: 0 0 -3em }
224 | #header :link, #header :visited, #header :link:hover, #header :visited:hover {
225 | background: transparent;
226 | color: #555;
227 | margin-bottom: 2px;
228 | border: none;
229 | padding: 0;
230 | }
231 | #header h1 :link:hover, #header h1 :visited:hover { color: #000 }
232 |
233 | /* Quick search */
234 | #search {
235 | clear: both;
236 | font-size: 10px;
237 | height: 2.2em;
238 | margin: 0 0 1em;
239 | text-align: right;
240 | }
241 | #search input { font-size: 10px }
242 | #search label { display: none }
243 |
244 | /* Navigation */
245 | .nav h2, .nav hr { display: none }
246 | .nav ul {
247 | font-size: 10px;
248 | list-style: none;
249 | margin: 0;
250 | padding: 0;
251 | text-align: right;
252 | }
253 | .nav li {
254 | border-right: 1px solid #d7d7d7;
255 | display: inline-block;
256 | padding: 0 .75em;
257 | white-space: nowrap;
258 | /* IE7 hack to make inline-block effective on block element */
259 | zoom: 1;
260 | *display: inline;
261 | }
262 | .nav li.last { border-right: none }
263 |
264 | /* Meta navigation bar */
265 | #metanav {
266 | padding-top: .3em;
267 | }
268 | #metanav form.trac-logout {
269 | display: inline;
270 | margin: 0;
271 | padding: 0;
272 | }
273 | #metanav form.trac-logout button {
274 | margin: 0;
275 | padding: 0;
276 | border: 0;
277 | outline: 0;
278 | background: transparent;
279 | font-family: inherit;
280 | font-size: 100%;
281 | color: #b00;
282 | border-bottom: 1px dotted #bbb;
283 | cursor: pointer;
284 | }
285 | #metanav form.trac-logout button::-moz-focus-inner { border: 0; padding: 0 }
286 | #metanav form.trac-logout button:hover { background-color: #eee; color: #555 }
287 | #metanav form.trac-logout button:active { position: static }
288 |
289 | /* Main navigation bar */
290 | #mainnav {
291 | font: normal 10px verdana,'Bitstream Vera Sans',helvetica,arial,sans-serif;
292 | box-shadow: 0 .5em 1.5em #eee;
293 | border: 1px solid #e4e4e4;
294 | border-radius: .5em;
295 | margin: .66em 0 .33em;
296 | }
297 | #mainnav li {
298 | background: white url(../topbar_gradient.png) 0 0;
299 | border: 1px solid #e4e4e4;
300 | margin: -1px .3em 0 -.4em;
301 | padding: .3em 0;
302 | }
303 |
304 | #mainnav .first, #mainnav .first :link {
305 | border-top-left-radius: .5em;
306 | border-bottom-left-radius: .5em;
307 | }
308 | #mainnav .last, #mainnav .last :link {
309 | border-top-right-radius: .5em;
310 | border-bottom-right-radius: .5em;
311 | margin-right: 0;
312 | }
313 |
314 | #mainnav :link, #mainnav :visited {
315 | border-bottom: none;
316 | box-shadow: 0 .1em .3em 0 #999;
317 | color: #000;
318 | padding: .3em 20px;
319 | }
320 |
321 | #mainnav li:hover {
322 | border: 1px solid #666;
323 | }
324 | #mainnav :link:hover, #mainnav :visited:hover {
325 | background: #000 url(../topbar_gradient2.png) 0 0 repeat-x;
326 | color: #eee;
327 | box-shadow: 0 .1em .6em 0 #666;
328 | }
329 |
330 | #mainnav .active {
331 | border: 1px solid #666;
332 | }
333 | #mainnav .active :link, #mainnav .active :visited {
334 | background: #000 url(../topbar_gradient2.png) 0 0 repeat-x;
335 | color: #eee;
336 | text-shadow: 1px 0 #eee;
337 | }
338 |
339 | /* Context-dependent navigation links */
340 | #ctxtnav {
341 | min-height: 1em;
342 | padding: .4em 0;
343 | }
344 | #ctxtnav li ul {
345 | background: #f7f7f7;
346 | color: #ccc;
347 | border: 1px solid;
348 | padding: 0;
349 | display: inline;
350 | margin: 0;
351 | }
352 | #ctxtnav li li { padding: 0; }
353 | #ctxtnav li li :link, #ctxtnav li li :visited { padding: 0 1em }
354 | #ctxtnav li li :link:hover, #ctxtnav li li :visited:hover {
355 | background: #bba;
356 | color: #fff;
357 | }
358 |
359 | .trac-nav, .trac-topnav {
360 | float: right;
361 | font-size: 80%;
362 | }
363 | .trac-topnav {
364 | margin-top: 14px;
365 | }
366 |
367 | /* Alternate links */
368 | #altlinks {
369 | clear: both;
370 | margin-top: .4em;
371 | text-align: center;
372 | }
373 | #altlinks h3 { font-size: 12px; letter-spacing: normal; margin: 0 }
374 | #altlinks ul { list-style: none; margin: 0; padding: 0 }
375 | #altlinks li {
376 | border-right: 1px solid #d7d7d7;
377 | display: inline;
378 | font-size: 11px;
379 | line-height: 1.5;
380 | padding: 0 1em;
381 | white-space: nowrap;
382 | }
383 | #altlinks li.last { border-right: none }
384 | #altlinks li :link, #altlinks li :visited {
385 | background-repeat: no-repeat;
386 | color: #666;
387 | border: none;
388 | padding: 0 0 2px;
389 | }
390 | #altlinks li a.ics {
391 | background: url(../ics.png) left center no-repeat;
392 | padding-left: 22px;
393 | }
394 | #altlinks li a.rss {
395 | background: url(../feed.png) left center no-repeat;
396 | padding-left: 20px;
397 | }
398 |
399 | /* Footer */
400 | #footer {
401 | clear: both;
402 | color: #bbb;
403 | font-size: 10px;
404 | height: 31px;
405 | padding: .25em 0;
406 | }
407 | #footer :link, #footer :visited { color: #bbb; }
408 | #footer hr { display: none }
409 | #footer #tracpowered { border: 0; float: left }
410 | #footer #tracpowered:hover { background: transparent }
411 | #footer p { margin: 0 }
412 | #footer p.left {
413 | float: left;
414 | margin-left: 1em;
415 | padding: 0 1em;
416 | border-left: 1px solid #d7d7d7;
417 | border-right: 1px solid #d7d7d7;
418 | }
419 | #footer p.right {
420 | float: right;
421 | text-align: right;
422 | }
423 |
424 | /* Container for all the elements in the page, usually single child of the body element */
425 | #content {
426 | position: relative; /* reference for absolute positioning of children */
427 | }
428 |
429 | /* Information content */
430 | div.trac-content {
431 | padding: .5em 1em;
432 | margin: .3em auto;
433 | border: 1px solid #e4e4e4;
434 | border-radius: .5em;
435 | box-shadow: .2em .3em 1.5em #eee;
436 | }
437 |
438 | /* Author info */
439 | .trac-author-anonymous, .trac-author-none {
440 | font-style: italic;
441 | }
442 | .trac-author-user {
443 | font-weight: bold;
444 | color: #666;
445 | }
446 |
447 | /* Help links */
448 | .uinohelp #help { display: none }
449 | #help {
450 | clear: both;
451 | color: #999;
452 | font-size: 90%;
453 | margin: 1em;
454 | text-align: right;
455 | }
456 | #help :link, #help :visited { cursor: help }
457 | #help hr { display: none }
458 |
459 | /* Datepicker hints */
460 | .trac-nodatetimehint .trac-datetimehint { display: none }
461 |
462 | /* Section folding */
463 | .foldable :link, .foldable :visited {
464 | background: url(../expanded.png) 4px 50% no-repeat;
465 | border: none;
466 | border-radius: .3em;
467 | box-shadow: .1em .1em .3em 0 #bbb;
468 | color: #222;
469 | text-shadow: .04em .04em #fcfcfc;
470 | padding: .3em .5em .3em 20px;
471 | }
472 | .foldable :link:hover, .foldable :visited:hover { background-color: transparent }
473 | .collapsed > .foldable :link, .collapsed > .foldable :visited {
474 | background-image: url(../collapsed.png);
475 | }
476 | .collapsed > div, .collapsed > table, .collapsed > ul, .collapsed > dl { display: none }
477 | fieldset > legend.foldable :link, fieldset > legend.foldable :visited {
478 | color: #666;
479 | font-size: 110%;
480 | text-shadow: .04em .04em #ddd;
481 | }
482 | .collapsed legend.foldable { background: white }
483 |
484 | .expander {
485 | background: url(../expander_normal.png) 0 50% no-repeat;
486 | cursor: pointer;
487 | padding-left: 8px;
488 | margin-left: 4px;
489 | }
490 | .expander:hover {
491 | background: url(../expander_normal_hover.png) 0 50% no-repeat;
492 | }
493 | .expander.expanded {
494 | background: url(../expander_open.png) 0 50% no-repeat;
495 | padding-left: 12px;
496 | margin-left: 0;
497 | }
498 | .expander.expanded:hover {
499 | background: url(../expander_open_hover.png) 0 50% no-repeat;
500 | }
501 |
502 | /* Section with count */
503 | h2 .trac-count, h3 .trac-count {
504 | margin-left: .2em;
505 | vertical-align: baseline;
506 | color: #999;
507 | font-size: 90%;
508 | font-weight: normal;
509 | }
510 |
511 | /* Page preferences form */
512 | #prefs {
513 | background: #f7f7f0;
514 | border: 1px outset #eee;
515 | border-radius: 1em;
516 | box-shadow: .2em .2em .7em 0 #777;
517 | float: right;
518 | font-size: 9px;
519 | padding: .8em;
520 | position: relative;
521 | margin: 0 1em 1em;
522 | }
523 | #prefs input, #prefs select { font-size: 9px; vertical-align: middle }
524 | #prefs fieldset {
525 | background: transparent;
526 | border: none;
527 | box-shadow: none;
528 | margin: .5em;
529 | padding: 0;
530 | }
531 | #prefs fieldset legend {
532 | background: transparent;
533 | color: #000;
534 | font-size: 9px;
535 | font-weight: normal;
536 | margin: 0 0 0 -1.5em;
537 | padding: 0;
538 | }
539 | #prefs .buttons { text-align: right }
540 |
541 | /* Version information (browser, wiki, attachments) */
542 | #info {
543 | margin: 1em 0 0 0;
544 | background: #f7f7f0;
545 | border: 1px solid #d7d7d7;
546 | border-collapse: collapse;
547 | border-spacing: 0;
548 | clear: both;
549 | width: 100%;
550 | }
551 | #info th, #info td { font-size: 85%; padding: 2px .5em; vertical-align: top }
552 | #info th { font-weight: bold; text-align: left }
553 | #info td.message { width: 100% }
554 | #info .message ul { padding: 0; margin: 0 2em }
555 | #info .message p { margin: 0; padding: 0 }
556 | a.trac-diff:after { content: "∆" }
557 |
558 | /* Wiki */
559 | .wikipage { padding-left: 18px }
560 | .wikipage h1, .wikipage h2, .wikipage h3 { margin-left: -18px }
561 | .wikipage table h1, .wikipage table h2, .wikipage table h3 { margin-left: 0px }
562 | div.compact > p:first-child { margin-top: 0 }
563 | div.compact > p:last-child { margin-bottom: 0 }
564 |
565 | /* Delete */
566 | #delete-confirm li { list-style-type: square; }
567 |
568 | /* Styles related to RTL support */
569 | .rtl { direction: rtl; }
570 | .rtl div.wiki-toc { float: left; }
571 | .rtl .wiki-toc ul ul, .wiki-toc ol ol { padding-right: 1.2em }
572 |
573 | a.missing:link, a.missing:visited, a.missing, span.missing,
574 | a.forbidden, span.forbidden { color: #998 }
575 | a.missing:hover { color: #000 }
576 | a.closed:link, a.closed:visited, span.closed { text-decoration: line-through }
577 |
578 | /* User-selectable styles for blocks */
579 | .important {
580 | background: #fcb;
581 | border: 1px dotted #d00;
582 | color: #500;
583 | padding: 0 .5em 0 .5em;
584 | margin: .5em;
585 | }
586 |
587 | dl.wiki dt { font-weight: bold }
588 | dl.compact dt { float: left; padding-right: .5em }
589 | dl.compact dd { margin: 0; padding: 0 }
590 |
591 | pre.wiki, pre.literal-block {
592 | background: #f7f7f7;
593 | border: 1px solid #d7d7d7;
594 | box-shadow: 0 0 1em #eee;
595 | border-radius: .3em;
596 | margin: 1em 1.75em;
597 | padding: .25em;
598 | overflow: auto;
599 | }
600 |
601 | div.wiki-code {
602 | margin: 1em 1.75em;
603 | }
604 |
605 | code, tt {
606 | color: #600;
607 | border: 1px solid #edc;
608 | border-radius: .25em;
609 | padding: 0 0.3em;
610 | background: #fafafa;
611 | }
612 |
613 | blockquote.citation {
614 | margin: -0.6em 0 0 0;
615 | border-style: solid;
616 | border-width: 0 0 0 2px;
617 | padding-left: .5em;
618 | border-color: #b44;
619 | }
620 | .citation blockquote.citation { border-color: #4b4; }
621 | .citation .citation blockquote.citation { border-color: #44b; }
622 | .citation .citation .citation blockquote.citation { border-color: #c55; }
623 |
624 | table.wiki {
625 | border: 1px solid #ccc;
626 | border-collapse: collapse;
627 | border-spacing: 0;
628 | box-shadow: 0 0 1em #eee; /* from pre.wiki */
629 | }
630 | table.wiki td { border: 1px solid #ccc; padding: .1em .25em; }
631 | table.wiki th {
632 | border: 1px solid #bbb;
633 | padding: .1em .25em;
634 | background-color: #f7f7f7;
635 | }
636 | table.wiki tbody tr.even { background-color: #fcfcfc }
637 | table.wiki tbody tr.odd { background-color: #f7f7f7 }
638 |
639 | .wikitoolbar {
640 | margin-top: 0.3em;
641 | border: solid #d7d7d7;
642 | border-width: 1px 1px 1px 0;
643 | height: 18px;
644 | width: 234px;
645 | }
646 | .wikitoolbar :link, .wikitoolbar :visited {
647 | background: transparent url(../edit_toolbar.png) no-repeat;
648 | border: 1px solid #fff;
649 | border-left-color: #d7d7d7;
650 | cursor: default;
651 | display: block;
652 | float: left;
653 | width: 24px;
654 | height: 16px;
655 | }
656 | .wikitoolbar :link:hover, .wikitoolbar :visited:hover {
657 | background-color: transparent;
658 | border: 1px solid #fb2;
659 | }
660 | .wikitoolbar a#em { background-position: 0 0 }
661 | .wikitoolbar a#strong { background-position: 0 -16px }
662 | .wikitoolbar a#heading { background-position: 0 -32px }
663 | .wikitoolbar a#link { background-position: 0 -48px }
664 | .wikitoolbar a#code { background-position: 0 -64px }
665 | .wikitoolbar a#hr { background-position: 0 -80px }
666 | .wikitoolbar a#np { background-position: 0 -96px }
667 | .wikitoolbar a#br { background-position: 0 -112px }
668 | .wikitoolbar a#img { background-position: 0 -128px }
669 |
670 | /* Textarea resizer */
671 | div.trac-resizable { display: table; width: 100% }
672 | div.trac-resizable > div { display: table-cell }
673 | div.trac-resizable textarea { display: block; margin-bottom: 0 }
674 | div.trac-grip {
675 | height: 5px;
676 | overflow: hidden;
677 | background: #eee url(../grip.png) no-repeat center 1px;
678 | border: 1px solid #ddd;
679 | border-top-width: 0;
680 | cursor: s-resize;
681 | }
682 |
683 | /* Styles for the list of attachments. */
684 | #attachments > div.attachments {
685 | padding: 0 0 1em 1em;
686 | }
687 | #attachments dl.attachments { margin-left: 2em; padding: 0 }
688 | #attachments dt { display: list-item; list-style: square; }
689 | #attachments dd { font-style: italic; margin-left: 0; padding-left: 0; }
690 | #attachments p {
691 | margin-left: 2em;
692 | font-size: 90%;
693 | color: #666;
694 | }
695 | #attachments > div.attachments > p {
696 | float: right;
697 | margin: .4em 0;
698 | }
699 | #attachments span.trac-author { font-style: italic; }
700 |
701 | .attachment #preview { margin-top: 1em }
702 |
703 | /* Styles for tabular listings such as those used for displaying directory
704 | contents and report results. */
705 | table.listing {
706 | clear: both;
707 | border-bottom: 1px solid #d7d7d7;
708 | border-collapse: collapse;
709 | border-spacing: 0;
710 | box-shadow: .2em .3em 1.5em #eee; /* from .trac-content */
711 | margin-top: 1em;
712 | width: 100%;
713 | }
714 | table.listing th { text-align: left; padding: 0 1em .1em 0; font-size: 12px }
715 | table.listing th.sel, table.listing td.sel { text-align: center; width: 1% }
716 | table.listing thead tr { background: #f7f7f0 }
717 | table.listing thead th {
718 | border: 1px solid #d7d7d7;
719 | border-bottom-color: #999;
720 | font-size: 11px;
721 | font-weight: bold;
722 | padding: 2px .5em;
723 | vertical-align: bottom;
724 | white-space: nowrap;
725 | }
726 | table.listing thead th :link:hover, table.listing thead th :visited:hover {
727 | background-color: transparent;
728 | }
729 | table.listing thead th a { border: none; padding-right: 12px }
730 | table.listing th.asc a, table.listing th.desc a {
731 | font-weight: bold;
732 | background-position: 100% 50%;
733 | background-repeat: no-repeat;
734 | }
735 | table.listing th.asc a { background-image: url(../asc.png) }
736 | table.listing th.desc a { background-image: url(../desc.png) }
737 | table.listing tbody td, table.listing tbody th {
738 | border: 1px dotted #ddd;
739 | padding: .3em .5em;
740 | vertical-align: top;
741 | }
742 | table.listing tbody td a:hover, table.listing tbody th a:hover {
743 | background-color: transparent;
744 | }
745 | table.listing tbody tr { border-top: 1px solid #ddd }
746 | table.listing tbody tr.even { background-color: #fcfcfc }
747 | table.listing tbody tr.odd { background-color: #f7f7f7 }
748 | table.listing tbody tr:hover td { background: #eed !important }
749 | table.listing tbody tr.focus { background: #ddf !important }
750 |
751 | table.listing pre { white-space: pre-wrap }
752 |
753 | /* Styles for the page history table
754 | (extends the styles for "table.listing") */
755 | #fieldhist td { padding: 0 .5em }
756 | #fieldhist td.date, #fieldhist td.diff, #fieldhist td.version,
757 | #fieldhist td.author {
758 | white-space: nowrap;
759 | }
760 | #fieldhist td.version { text-align: center }
761 | #fieldhist td.comment { width: 100% }
762 | #fieldhist .inlinebuttons {
763 | display: none;
764 | float: right;
765 | }
766 | .uisymbols #fieldhist .inlinebuttons a { font-size: 85% }
767 |
768 | /* Auto-completion interface */
769 | .suggestions { background: #fff; border: 1px solid #886; color: #222; }
770 | .suggestions ul {
771 | font-family: sans-serif;
772 | max-height: 20em;
773 | min-height: 3em;
774 | list-style: none;
775 | margin: 0;
776 | overflow: auto;
777 | padding: 0;
778 | width: 440px;
779 | }
780 | * html .suggestions ul { height: 10em; }
781 | .suggestions li { background: #fff; cursor: pointer; padding: 2px 5px }
782 | .suggestions li.selected { background: #b9b9b9 }
783 |
784 | /* Styles for the error page */
785 | #content.error .message, div.system-message {
786 | background: #fdc;
787 | border: 2px solid #d00;
788 | color: #500;
789 | padding: .5em;
790 | margin: 1em 0;
791 | }
792 | #content.error div.message pre, div.system-message pre {
793 | margin-left: 1em;
794 | overflow: hidden;
795 | white-space: pre-wrap;
796 | }
797 |
798 | div.system-message p { margin: 0 }
799 | div.system-message p.system-message-title { font-weight: bold; }
800 |
801 | /* rst errors are less emphasized */
802 | span.system-message {
803 | float: left;
804 | background: #fdc;
805 | padding: 0.2em;
806 | margin: 1px;
807 | border-radius: 0.25em;
808 | }
809 |
810 | /* whole-page admonitions in theme.html */
811 | div.system-message {
812 | border-radius: .5em;
813 | /* taken from #prefs */
814 | box-shadow: .2em .2em .7em 0 #777;
815 | }
816 |
817 | #warning.system-message, .warning.system-message {
818 | background: #ffb;
819 | border: 1px solid #500;
820 | }
821 | #warning.system-message li { list-style-type: square; }
822 |
823 | #notice.system-message, .notice.system-message {
824 | background: #dfd;
825 | border: 1px solid #500;
826 | }
827 | #notice.system-message li { list-style-type: square; }
828 |
829 | div.system-message .trac-close-msg {
830 | display: none; /* hidden when no Javascript available */
831 | float: right;
832 | font-size: 80%;
833 | /* taken from .inlinebuttons */
834 | border: 1px solid #ddd;
835 | border-radius: 1em;
836 | height: 1.6em;
837 | padding: 0 .4em .1em;
838 | box-shadow: none;
839 | margin: 0 .1em .1em 0;
840 | background: none;
841 | color: #999;
842 | /* taken from trac-delete */
843 | color: #d31313;
844 | /* override */
845 | height: 1.4em;
846 | }
847 | div.system-message .trac-close-msg:hover {
848 | /* taken from .inlinebuttons */
849 | background: #f6f6f6;
850 | color: #333;
851 | text-shadow: .04em .04em #fcfcfc;
852 | box-shadow: .1em .1em .6em 0 #999;
853 | }
854 | .uisymbols div.system-message .trac-close-msg > span {
855 | display: none;
856 | }
857 | .uisymbols div.system-message .trac-close-msg:after {
858 | content: "x"; /* or – "–"; */
859 | }
860 |
861 | /* error.html page */
862 | #content.error form.newticket { display: inline; }
863 | #content.error form.newticket textarea { display: none; }
864 |
865 | #content.error #traceback { margin-left: 1em; }
866 | #content.error #traceback :link, #content.error #traceback :visited {
867 | border: none;
868 | }
869 | #content.error #tbtoggle { font-size: 80%; }
870 | #content.error #traceback div { margin-left: 1em; }
871 | #content.error #traceback h3 { font-size: 95%; margin: .5em 0 0; }
872 | #content.error #traceback :link var, #content.error #traceback :visited var {
873 | font-family: monospace;
874 | font-style: normal;
875 | font-weight: bold;
876 | }
877 | #content.error #traceback span.file { color: #666; font-size: 85%; }
878 | #content.error #traceback ul { list-style: none; margin: .5em 0; padding: 0; }
879 | #content.error #traceback table.code td { white-space: pre; font-size: 90%; }
880 | #content.error #traceback table.code tr.current td { background: #e6e6e6; }
881 | #content.error #traceback table { margin: .5em 0 1em; }
882 | #content.error #traceback th, #content.error #traceback td {
883 | font-size: 85%; padding: 1px;
884 | }
885 | #content.error #traceback th var {
886 | font-family: monospace;
887 | font-style: normal;
888 | }
889 | #content.error #traceback td code { white-space: pre; }
890 | #content.error #traceback pre { font-size: 95%; }
891 |
892 | #content .paging { margin: 0 0 2em; padding: .5em 0 0;
893 | font-size: 85%; line-height: 2em; text-align: center;
894 | }
895 | #content .paging .current {
896 | padding: .1em .3em;
897 | border: 1px solid #333;
898 | background: #999; color: #fff;
899 | }
900 |
901 | #content .paging :link, #content .paging :visited {
902 | padding: .1em .3em;
903 | border: 1px solid #666;
904 | background: transparent; color: #666;
905 | }
906 | #content .paging :link:hover, #content .paging :visited:hover {
907 | background: #999; color: #fff; border-color: #333;
908 | }
909 | #content .paging .previous a,
910 | #content .paging .next a {
911 | font-size: 150%; font-weight: bold; border: none;
912 | }
913 | #content .paging .previous a:hover,
914 | #content .paging .next a:hover {
915 | background: transparent; color: #666;
916 | }
917 |
918 | #content h2 .numresults { color: #666; font-size: 90%; }
919 |
920 | /* Styles for the About page */
921 | #content.about a.logo { border: none; float: right; margin-left: 2em; }
922 | #content.about a.logo img { display: block; }
923 | #content.about #python-logo img { width: 140px; height: 56px; }
924 | #content.about p.copyright { color: #999; font-size: 90%; }
925 |
926 | /* Styles for environment info (error and about pages) */
927 | #environmentinfo h2 { margin-top: 2em; }
928 | #environmentinfo table { margin: 1em; width: auto; }
929 | #environmentinfo table th, #environmentinfo table td { border: 1px solid #ddd; font-size: 90%; }
930 | #environmentinfo table th { background: #f7f7f7; font-weight: bold; white-space: nowrap; }
931 | #environmentinfo table td.file { color: #666; }
932 | #environmentinfo table tr.disabled th,
933 | #environmentinfo table tr.disabled td { color: #999; }
934 | #environmentinfo #config th, #content.about #config td { padding: 3px; }
935 | #environmentinfo #config tr.modified { background: #ffd; }
936 | #environmentinfo #config tr.modified td.value { font-style: italic; }
937 | #environmentinfo #config td.doc { padding: 3px 1em; }
938 |
939 | /* Styles for jquery-ui */
940 | .trac-placeholder { background: #eed }
941 |
942 | /* Styles for search word highlighting */
943 | @media screen {
944 | .searchword0 { background: #ff9 }
945 | .searchword1 { background: #cfc }
946 | .searchword2 { background: #cff }
947 | .searchword3 { background: #ccf }
948 | .searchword4 { background: #fcf }
949 | }
950 |
951 | @media print {
952 | #header, #altlinks, #footer, #help, #warning, #notice { display: none }
953 | .nav, form, .buttons form, form .buttons, form .inlinebuttons,
954 | .noprint, .trac-nav, .trac-topnav,
955 | #attachments > div.attachments > p {
956 | display: none;
957 | }
958 | form.printableform { display: block }
959 | div.code pre { white-space: pre-wrap }
960 | :link, :visited { border-bottom: none }
961 | div.trac-content { box-shadow: none }
962 | .foldable :link, .foldable :visited {
963 | text-shadow: none;
964 | box-shadow: none;
965 | }
966 | }
967 |
--------------------------------------------------------------------------------
/doc/SoulseekProtocol – Museek+_files/trac.js:
--------------------------------------------------------------------------------
1 | (function($){
2 |
3 | if (typeof _ == 'undefined')
4 | babel.Translations.load({}).install();
5 |
6 | $.fn.addAnchor = function(title) {
7 | title = title || _("Link here");
8 | return this.filter("*[id]").each(function() {
9 | $(" \u00B6").attr("href", "#" + this.id)
10 | .attr("title", title).appendTo(this);
11 | });
12 | };
13 |
14 | $.fn.checked = function(checked) {
15 | if (checked == undefined) { // getter
16 | if (!this.length) return false;
17 | return this.get(0).checked;
18 | } else { // setter
19 | return this.each(function() {
20 | this.checked = checked;
21 | });
22 | }
23 | };
24 |
25 | // Add a Select All checkbox to each thead in the table.
26 | $.fn.addSelectAllCheckboxes = function() {
27 | var $table = this;
28 | if ($("tr td.sel", $table).length > 0) {
29 | $("tr th.sel", $table).append(
30 | $('').attr({
31 | title: _("Toggle group")
32 | }).click(function() {
33 | $("tr td.sel input",
34 | $(this).closest("thead, tbody").next())
35 | .prop("checked", this.checked).change();
36 | })
37 | );
38 | $("tr td.sel", $table).click(function() {
39 | var $tbody = $(this).closest("tbody");
40 | var $checkboxes = $("tr td.sel input", $tbody);
41 | var num_selected = $checkboxes.filter(":checked").length;
42 | var none_selected = num_selected === 0;
43 | var all_selected = num_selected === $checkboxes.length;
44 | $("tr th.sel input", $tbody.prev())
45 | .prop({"checked": all_selected,
46 | "indeterminate": !(none_selected || all_selected)});
47 | });
48 | }
49 | };
50 |
51 | // Conditionally disable the submit button. Returns a jQuery object.
52 | $.fn.disableSubmit = function(determinant) {
53 | determinant = $(determinant);
54 | var subject = $(this);
55 | var isDisabled;
56 | if (determinant.is("input:checkbox")) {
57 | isDisabled = function () {
58 | return determinant.filter(":checked").length === 0;
59 | }
60 | } else if (determinant.is("input:file")) {
61 | isDisabled = function () {
62 | return !determinant.val();
63 | }
64 | } else {
65 | return subject;
66 | }
67 | function toggleDisabled() {
68 | subject.prop("disabled", isDisabled);
69 | if (subject.prop("disabled")) {
70 | subject.attr("title", _("At least one item must be selected"))
71 | } else {
72 | subject.removeAttr("title");
73 | }
74 | }
75 | determinant.change(toggleDisabled);
76 | toggleDisabled();
77 | return subject;
78 | };
79 |
80 | $.fn.enable = function(enabled) {
81 | if (enabled == undefined) enabled = true;
82 | return this.each(function() {
83 | this.disabled = !enabled;
84 | var label = $(this).parents("label");
85 | if (!label.length && this.id) {
86 | label = $("label[for='" + this.id + "']");
87 | }
88 | if (!enabled) {
89 | label.addClass("disabled");
90 | } else {
91 | label.removeClass("disabled");
92 | }
93 | });
94 | };
95 |
96 | $.fn.getAbsolutePos = function() {
97 | return this.map(function() {
98 | var left = this.offsetLeft;
99 | var top = this.offsetTop;
100 | var parent = this.offsetParent;
101 | while (parent) {
102 | left += parent.offsetLeft;
103 | top += parent.offsetTop;
104 | parent = parent.offsetParent;
105 | }
106 | return {left: left, top: top};
107 | });
108 | };
109 |
110 | $.fn.scrollToTop = function() {
111 | return this.each(function() {
112 | scrollTo(0, $(this).getAbsolutePos()[0].top);
113 | return false;
114 | });
115 | };
116 |
117 | // Disable the form's submit action after the submit button is pressed by
118 | // replacing it with a handler that cancels the action. The handler is
119 | // removed when navigating away from the page so that the action will
120 | // be enabled when using the back button to return to the page.
121 | $.fn.disableOnSubmit = function() {
122 | this.click(function() {
123 | var form = $(this).closest("form");
124 | if (form.hasClass("trac-submit-is-disabled")) {
125 | form.bind("submit.prevent-submit", function() {
126 | return false;
127 | });
128 | $(window).on("unload", function() {
129 | form.unbind("submit.prevent-submit");
130 | });
131 | } else {
132 | form.addClass("trac-submit-is-disabled");
133 | $(window).on("unload", function() {
134 | form.removeClass("trac-submit-is-disabled");
135 | })
136 | }
137 | });
138 | };
139 |
140 | $.loadStyleSheet = function(href, type) {
141 | type = type || "text/css";
142 | $(document).ready(function() {
143 | var link;
144 | $("link[rel=stylesheet]").each(function() {
145 | if (this.getAttribute("href") === href) {
146 | if (this.disabled)
147 | this.disabled = false;
148 | link = this;
149 | return false;
150 | }
151 | });
152 | if (link !== undefined)
153 | return;
154 | if (document.createStyleSheet) { // MSIE
155 | document.createStyleSheet(href);
156 | } else {
157 | $("")
158 | .appendTo("head");
159 | }
160 | });
161 | };
162 |
163 | // {script.src: [listener1, listener2, ...]}
164 | var readyListeners = {};
165 |
166 | $.documentReady = function(listener) {
167 | var script = document.currentScript;
168 | if (script === undefined) {
169 | script = $("head script");
170 | script = script[script.length - 1];
171 | }
172 | if (script) {
173 | var href = script.getAttribute("src");
174 | if (!(href in readyListeners))
175 | readyListeners[href] = [];
176 | var listeners = readyListeners[href];
177 | listeners.push(listener);
178 | }
179 | $(document).ready(listener);
180 | };
181 |
182 | $.loadScript = function(href, type, charset) {
183 | var script;
184 | $("head script").each(function() {
185 | if (this.getAttribute("src") === href) {
186 | script = this;
187 | return false;
188 | }
189 | });
190 | if (script !== undefined) {
191 | // Call registered ready listeners
192 | $.each(readyListeners[href] || [], function(idx, listener) {
193 | listener.call(document, $);
194 | });
195 | } else {
196 | // Don't use $("