Compare commits
14 Commits
m/mkv-subt
...
v0.13.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b6c9ffcdb | ||
|
|
1d4b8ab67d | ||
|
|
6404168bee | ||
|
|
6613366cff | ||
|
|
74349129f4 | ||
|
|
cdc2c1d718 | ||
|
|
f8cc155650 | ||
|
|
b6bdeab50b | ||
|
|
f528f6033f | ||
|
|
ef1bc13c38 | ||
|
|
2f54feac74 | ||
|
|
75d30baaa5 | ||
|
|
990fb57839 | ||
|
|
1883341ddb |
@@ -1,4 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 'node'
|
||||
install: npm install standard
|
||||
install: npm install standard depcheck
|
||||
|
||||
@@ -28,5 +28,8 @@
|
||||
- Rémi Jouannet (remijouannet@gmail.com)
|
||||
- Andrea Tupini (tupini07@gmail.com)
|
||||
- grunjol (grunjol@gmail.com)
|
||||
- Jason Kurian (jasonk92@gmail.com)
|
||||
- Vamsi Krishna Avula (vamsi_ism@outlook.com)
|
||||
- Noam Okman (noamokman@gmail.com)
|
||||
|
||||
#### Generated by bin/update-authors.sh.
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
||||
# WebTorrent Desktop Version History
|
||||
|
||||
## v0.13.0 - 2016-08-31
|
||||
|
||||
### Added
|
||||
- Torrent progress bar
|
||||
- Support .m4a audio
|
||||
- Better telemetry: log error versions, report more types of errors
|
||||
|
||||
### Changed
|
||||
- New look - Material UI. Rewrote Create Torrent and Preferences pages.
|
||||
|
||||
### Fixed
|
||||
- Fixed telemetry [object Object] and [object HTMLMediaElement] bugs
|
||||
- Don't render player controls when playing externally, eg in VLC
|
||||
- Don't play notification sounds during media playback
|
||||
|
||||
## v0.12.0 - 2016-08-23
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var fs = require('fs')
|
||||
var cp = require('child_process')
|
||||
|
||||
// We can't use `builtin-modules` here since our TravisCI
|
||||
// setup expects this file to run with no dependencies
|
||||
var BUILT_IN_NODE_MODULES = [
|
||||
'assert',
|
||||
'buffer',
|
||||
'child_process',
|
||||
'cluster',
|
||||
'console',
|
||||
'constants',
|
||||
'crypto',
|
||||
'dgram',
|
||||
'dns',
|
||||
'domain',
|
||||
'events',
|
||||
'fs',
|
||||
'http',
|
||||
'https',
|
||||
'module',
|
||||
'net',
|
||||
'os',
|
||||
'path',
|
||||
'process',
|
||||
'punycode',
|
||||
'querystring',
|
||||
'readline',
|
||||
'repl',
|
||||
'stream',
|
||||
'string_decoder',
|
||||
'timers',
|
||||
'tls',
|
||||
'tty',
|
||||
'url',
|
||||
'util',
|
||||
'v8',
|
||||
'vm',
|
||||
'zlib'
|
||||
]
|
||||
|
||||
var BUILT_IN_ELECTRON_MODULES = [ 'electron' ]
|
||||
|
||||
var BUILT_IN_DEPS = [].concat(BUILT_IN_NODE_MODULES, BUILT_IN_ELECTRON_MODULES)
|
||||
|
||||
var EXECUTABLE_DEPS = [
|
||||
'babel-cli',
|
||||
'babel-plugin-syntax-jsx',
|
||||
'babel-plugin-transform-es2015-destructuring',
|
||||
'babel-plugin-transform-object-rest-spread',
|
||||
'babel-plugin-transform-react-jsx',
|
||||
'gh-release',
|
||||
'nodemon',
|
||||
'standard'
|
||||
]
|
||||
|
||||
main()
|
||||
|
||||
// Scans codebase for missing or unused dependencies. Exits with code 0 on success.
|
||||
function main () {
|
||||
if (process.platform === 'win32') {
|
||||
console.error('Sorry, check-deps only works on Mac and Linux')
|
||||
return
|
||||
}
|
||||
|
||||
var usedDeps = findUsedDeps()
|
||||
var packageDeps = findPackageDeps()
|
||||
|
||||
var missingDeps = usedDeps.filter(
|
||||
(dep) => !includes(packageDeps, dep) && !includes(BUILT_IN_DEPS, dep)
|
||||
)
|
||||
var unusedDeps = packageDeps.filter(
|
||||
(dep) => !includes(usedDeps, dep) && !includes(EXECUTABLE_DEPS, dep)
|
||||
)
|
||||
|
||||
if (missingDeps.length > 0) {
|
||||
console.error('Missing package dependencies: ' + missingDeps)
|
||||
}
|
||||
if (unusedDeps.length > 0) {
|
||||
console.error('Unused package dependencies: ' + unusedDeps)
|
||||
}
|
||||
if (missingDeps.length + unusedDeps.length > 0) {
|
||||
process.exitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Finds all dependencies specified in `package.json`
|
||||
function findPackageDeps () {
|
||||
var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
||||
|
||||
var deps = Object.keys(pkg.dependencies)
|
||||
var devDeps = Object.keys(pkg.devDependencies)
|
||||
var optionalDeps = Object.keys(pkg.optionalDependencies)
|
||||
|
||||
return [].concat(deps, devDeps, optionalDeps)
|
||||
}
|
||||
|
||||
// Finds all dependencies that used with `require()`
|
||||
function findUsedDeps () {
|
||||
var stdout = cp.execSync('./bin/list-deps.sh')
|
||||
return stdout.toString().trim().split('\n')
|
||||
}
|
||||
|
||||
function includes (arr, elem) {
|
||||
return arr.indexOf(elem) >= 0
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
# This is a truly heinous hack, but it works pretty nicely.
|
||||
# Find all modules we're requiring---even conditional requires.
|
||||
|
||||
grep "require('" src/ bin/ -R |
|
||||
grep '.js:' |
|
||||
sed "s/.*require('\([^'\/]*\).*/\1/" |
|
||||
grep -v '^\.' |
|
||||
sort |
|
||||
uniq
|
||||
@@ -7,4 +7,4 @@ npm run package -- --sign
|
||||
git push
|
||||
git push --tags
|
||||
npm publish
|
||||
./node_modules/.bin/gh-release
|
||||
npm run gh-release
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "webtorrent-desktop",
|
||||
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
||||
"version": "0.12.0",
|
||||
"version": "0.13.1",
|
||||
"author": {
|
||||
"name": "WebTorrent, LLC",
|
||||
"email": "feross@webtorrent.io",
|
||||
@@ -52,6 +52,7 @@
|
||||
"babel-plugin-transform-object-rest-spread": "^6.8.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||
"cross-zip": "^2.0.1",
|
||||
"depcheck": "^0.6.4",
|
||||
"electron-osx-sign": "^0.3.0",
|
||||
"electron-packager": "^7.0.0",
|
||||
"electron-winstaller": "^2.3.0",
|
||||
@@ -97,7 +98,8 @@
|
||||
"package": "node ./bin/package.js",
|
||||
"prepublish": "npm run build",
|
||||
"start": "npm run build && electron .",
|
||||
"test": "standard && node ./bin/check-deps.js",
|
||||
"test": "standard && depcheck --ignores=babel-cli,nodemon,gh-release --ignore-dirs=build,dist",
|
||||
"gh-release": "gh-release",
|
||||
"update-authors": "./bin/update-authors.sh",
|
||||
"watch": "nodemon --exec 'npm run start' --ext js,pug,css"
|
||||
}
|
||||
|
||||
@@ -39,14 +39,11 @@ class ShowMore extends React.Component {
|
||||
? this.props.hideLabel
|
||||
: this.props.showLabel
|
||||
return (
|
||||
<div
|
||||
style={this.props.style}
|
||||
>
|
||||
<div style={this.props.style}>
|
||||
{this.state.expanded ? this.props.children : null}
|
||||
<FlatButton
|
||||
onClick={this.handleClick}
|
||||
label={label}
|
||||
/>
|
||||
label={label} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,7 +126,9 @@ function logUncaughtError (procName, e) {
|
||||
|
||||
var message
|
||||
var stack = ''
|
||||
if (e.message) {
|
||||
if (e == null) {
|
||||
message = 'Unexpected undefined error'
|
||||
} else if (e.message) {
|
||||
// err is either an Error or a plain object {message, stack}
|
||||
message = e.message
|
||||
stack = e.stack
|
||||
@@ -149,11 +151,13 @@ function logUncaughtError (procName, e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof stack !== 'string') stack = 'Unexpected stack: ' + stack
|
||||
if (typeof message !== 'string') message = 'Unexpected message: ' + message
|
||||
|
||||
// Remove the first part of each file path in the stack trace.
|
||||
// - Privacy: remove personal info like C:\Users\<full name>
|
||||
// - Aggregation: this lets us find which stacktraces occur often
|
||||
if (stack && typeof stack === 'string') stack = stack.replace(/\(.*app.asar/g, '(...')
|
||||
else if (stack) stack = 'Unexpected stack: ' + stack
|
||||
stack = stack.replace(/\(.*app.asar/g, '(...')
|
||||
|
||||
// We need to POST the telemetry object, make sure it stays < 100kb
|
||||
if (telemetry.uncaughtErrors.length > 20) return
|
||||
|
||||
@@ -8,6 +8,7 @@ const {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||
const FlatButton = require('material-ui/FlatButton').default
|
||||
const RaisedButton = require('material-ui/RaisedButton').default
|
||||
const TextField = require('material-ui/TextField').default
|
||||
const Checkbox = require('material-ui/Checkbox').default
|
||||
|
||||
const CreateTorrentErrorPage = require('../components/create-torrent-error-page')
|
||||
const Heading = require('../components/Heading')
|
||||
@@ -20,27 +21,20 @@ class CreateTorrentPage extends React.Component {
|
||||
var state = this.props.state
|
||||
var info = state.location.current()
|
||||
|
||||
// Preprocess: exclude .DS_Store and other dotfiles
|
||||
var files = info.files
|
||||
.filter((f) => !f.name.startsWith('.'))
|
||||
.map((f) => ({name: f.name, path: f.path, size: f.size}))
|
||||
if (files.length === 0) return (<CreateTorrentErrorPage state={state} />)
|
||||
|
||||
// First, extract the base folder that the files are all in
|
||||
var pathPrefix = info.folderPath
|
||||
if (!pathPrefix) {
|
||||
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
|
||||
pathPrefix = info.files.map((x) => x.path).reduce(findCommonPrefix)
|
||||
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
||||
pathPrefix = path.dirname(pathPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check: show the number of files and total size
|
||||
var numFiles = files.length
|
||||
var totalBytes = files
|
||||
.map((f) => f.size)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
|
||||
// Then, exclude .DS_Store and other dotfiles
|
||||
var files = info.files
|
||||
.filter((f) => !containsDots(f.path, pathPrefix))
|
||||
.map((f) => ({name: f.name, path: f.path, size: f.size}))
|
||||
if (files.length === 0) return (<CreateTorrentErrorPage state={state} />)
|
||||
|
||||
// Then, use the name of the base folder (or sole file, for a single file torrent)
|
||||
// as the default name. Show all files relative to the base folder.
|
||||
@@ -54,90 +48,52 @@ class CreateTorrentPage extends React.Component {
|
||||
defaultName = path.basename(pathPrefix)
|
||||
basePath = path.dirname(pathPrefix)
|
||||
}
|
||||
var maxFileElems = 100
|
||||
var fileElems = files.slice(0, maxFileElems).map(function (file, i) {
|
||||
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
|
||||
return (<div key={i}>{relativePath}</div>)
|
||||
})
|
||||
if (files.length > maxFileElems) {
|
||||
fileElems.push(<div key='more'>+ {maxFileElems - files.length} more</div>)
|
||||
}
|
||||
|
||||
// Default trackers
|
||||
var trackers = createTorrent.announceList.join('\n')
|
||||
|
||||
this.state = {
|
||||
comment: '',
|
||||
isPrivate: false,
|
||||
pathPrefix,
|
||||
basePath,
|
||||
defaultName,
|
||||
fileElems,
|
||||
torrentInfo,
|
||||
files,
|
||||
trackers
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit () {
|
||||
var announceList = document.querySelector('.torrent-trackers').value
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '')
|
||||
var isPrivate = document.querySelector('.torrent-is-private').checked
|
||||
var comment = document.querySelector('.torrent-comment').value.trim()
|
||||
var options = {
|
||||
// We can't let the user choose their own name if we want WebTorrent
|
||||
// to use the files in place rather than creating a new folder.
|
||||
// If we ever want to add support for that:
|
||||
// name: document.querySelector('.torrent-name').value
|
||||
name: this.state.defaultName,
|
||||
path: this.state.basePath,
|
||||
files: this.state.files,
|
||||
announce: announceList,
|
||||
private: isPrivate,
|
||||
comment: comment
|
||||
}
|
||||
dispatch('createTorrent', options)
|
||||
// Create React event handlers only once
|
||||
this.setIsPrivate = (_, isPrivate) => this.setState({isPrivate})
|
||||
this.setComment = (_, comment) => this.setState({comment})
|
||||
this.setTrackers = (_, trackers) => this.setState({trackers})
|
||||
this.handleSubmit = handleSubmit.bind(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
var files = this.state.files
|
||||
|
||||
// Sanity check: show the number of files and total size
|
||||
var numFiles = files.length
|
||||
var totalBytes = files
|
||||
.map((f) => f.size)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
|
||||
|
||||
return (
|
||||
<div className='create-torrent'>
|
||||
<Heading level={1}>
|
||||
Create torrent "{this.state.defaultName}"
|
||||
</Heading>
|
||||
<div className='torrent-info'>
|
||||
{this.state.torrentInfo}
|
||||
</div>
|
||||
<Heading level={1}>Create torrent {this.state.defaultName}</Heading>
|
||||
<div className='torrent-info'>{torrentInfo}</div>
|
||||
<div className='torrent-attribute'>
|
||||
<label>Path:</label>
|
||||
<div className='torrent-attribute'>{this.state.pathPrefix}</div>
|
||||
<div>{this.state.pathPrefix}</div>
|
||||
</div>
|
||||
<ShowMore
|
||||
style={{
|
||||
marginBottom: 10
|
||||
}}
|
||||
hideLabel='Hide advanced settings...'
|
||||
showLabel='Show advanced settings...'
|
||||
>
|
||||
<div key='advanced' className='create-torrent-advanced'>
|
||||
<div key='private' className='torrent-attribute'>
|
||||
<label>Private:</label>
|
||||
<input type='checkbox' className='torrent-is-private' value='torrent-is-private' />
|
||||
</div>
|
||||
<Heading level={2}>Trackers:</Heading>
|
||||
<TextField
|
||||
className='torrent-trackers'
|
||||
hintText='Tracker'
|
||||
multiLine
|
||||
rows={2}
|
||||
rowsMax={10}
|
||||
defaultValue={this.state.trackers}
|
||||
/>
|
||||
<div key='comment' className='torrent-attribute'>
|
||||
<label>Comment:</label>
|
||||
<textarea className='torrent-attribute torrent-comment' />
|
||||
</div>
|
||||
<div key='files' className='torrent-attribute'>
|
||||
<label>Files:</label>
|
||||
<div>{this.state.fileElems}</div>
|
||||
</div>
|
||||
</div>
|
||||
showLabel='Show advanced settings...' >
|
||||
{this.renderAdvanced()}
|
||||
</ShowMore>
|
||||
<div className='float-right'>
|
||||
<FlatButton
|
||||
@@ -156,6 +112,83 @@ class CreateTorrentPage extends React.Component {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderAdvanced () {
|
||||
// Create file list
|
||||
var maxFileElems = 100
|
||||
var files = this.state.files
|
||||
var fileElems = files.slice(0, maxFileElems).map((file, i) => {
|
||||
var relativePath = path.relative(this.state.pathPrefix, file.path)
|
||||
return (<div key={i}>{relativePath}</div>)
|
||||
})
|
||||
if (files.length > maxFileElems) {
|
||||
fileElems.push(<div key='more'>+ {files.length - maxFileElems} more</div>)
|
||||
}
|
||||
|
||||
// Align the text fields
|
||||
var textFieldStyle = { width: '' }
|
||||
var textareaStyle = { margin: 0 }
|
||||
|
||||
return (
|
||||
<div key='advanced' className='create-torrent-advanced'>
|
||||
<div key='private' className='torrent-attribute'>
|
||||
<label>Private:</label>
|
||||
<Checkbox
|
||||
className='torrent-is-private'
|
||||
style={{display: ''}}
|
||||
checked={this.state.isPrivate}
|
||||
onCheck={this.setIsPrivate} />
|
||||
</div>
|
||||
<div key='trackers' className='torrent-attribute'>
|
||||
<label>Trackers:</label>
|
||||
<TextField
|
||||
className='torrent-trackers'
|
||||
style={textFieldStyle}
|
||||
textareaStyle={textareaStyle}
|
||||
multiLine
|
||||
rows={2}
|
||||
rowsMax={10}
|
||||
value={this.state.trackers}
|
||||
onChange={this.setTrackers} />
|
||||
</div>
|
||||
<div key='comment' className='torrent-attribute'>
|
||||
<label>Comment:</label>
|
||||
<TextField
|
||||
className='torrent-comment'
|
||||
style={textFieldStyle}
|
||||
textareaStyle={textareaStyle}
|
||||
hintText='Optionally describe your torrent...'
|
||||
multiLine
|
||||
rows={2}
|
||||
rowsMax={10}
|
||||
value={this.state.comment}
|
||||
onChange={this.setComment} />
|
||||
</div>
|
||||
<div key='files' className='torrent-attribute'>
|
||||
<label>Files:</label>
|
||||
<div>{fileElems}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit () {
|
||||
var announceList = this.state.trackers
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '')
|
||||
var options = {
|
||||
// We can't let the user choose their own name if we want WebTorrent
|
||||
// to use the files in place rather than creating a new folder.
|
||||
name: this.state.defaultName,
|
||||
path: this.state.basePath,
|
||||
files: this.state.files,
|
||||
announce: announceList,
|
||||
private: this.state.isPrivate,
|
||||
comment: this.state.comment.trim()
|
||||
}
|
||||
dispatch('createTorrent', options)
|
||||
}
|
||||
|
||||
// Finds the longest common prefix
|
||||
@@ -168,4 +201,9 @@ function findCommonPrefix (a, b) {
|
||||
return a.substring(0, i)
|
||||
}
|
||||
|
||||
function containsDots (path, pathPrefix) {
|
||||
var suffix = path.substring(pathPrefix.length).replace(/\\/g, '/')
|
||||
return ('/' + suffix).includes('/.')
|
||||
}
|
||||
|
||||
module.exports = CreateTorrentPage
|
||||
|
||||
@@ -96,6 +96,14 @@ class PreferencesPage extends React.Component {
|
||||
}
|
||||
|
||||
setDefaultAppButton () {
|
||||
var isFileHandler = this.props.state.unsaved.prefs.isFileHandler
|
||||
if (isFileHandler) {
|
||||
return (
|
||||
<Preference>
|
||||
<p>WebTorrent is your default torrent app. Hooray!</p>
|
||||
</Preference>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Preference>
|
||||
<p>WebTorrent is not currently the default torrent app.</p>
|
||||
@@ -109,20 +117,17 @@ class PreferencesPage extends React.Component {
|
||||
}
|
||||
|
||||
handleSetDefaultApp () {
|
||||
window.alert('TODO')
|
||||
// var isFileHandler = state.unsaved.prefs.isFileHandler
|
||||
// dispatch('updatePreferences', 'isFileHandler', !isFileHandler)
|
||||
dispatch('updatePreferences', 'isFileHandler', true)
|
||||
}
|
||||
|
||||
render () {
|
||||
var style = {
|
||||
color: colors.grey400,
|
||||
marginLeft: 25,
|
||||
marginRight: 25
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
color: colors.grey400,
|
||||
marginLeft: 25,
|
||||
marginRight: 25
|
||||
}}
|
||||
>
|
||||
<div style={style} >
|
||||
<PreferencesSection title='Downloads'>
|
||||
{this.downloadPathSelector()}
|
||||
</PreferencesSection>
|
||||
@@ -146,13 +151,12 @@ class PreferencesSection extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
var style = {
|
||||
marginBottom: 25,
|
||||
marginTop: 25
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 25,
|
||||
marginTop: 25
|
||||
}}
|
||||
>
|
||||
<div style={style}>
|
||||
<Heading level={2}>{this.props.title}</Heading>
|
||||
{this.props.children}
|
||||
</div>
|
||||
@@ -162,15 +166,8 @@ class PreferencesSection extends React.Component {
|
||||
|
||||
class Preference extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
var style = { marginBottom: 10 }
|
||||
return (<div style={style}>{this.props.children}</div>)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,10 +272,12 @@ table {
|
||||
.create-torrent {
|
||||
padding: 10px 25px;
|
||||
overflow: hidden;
|
||||
font: 16px/24px BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.create-torrent .torrent-attribute {
|
||||
white-space: nowrap;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.create-torrent .torrent-attribute>* {
|
||||
@@ -283,13 +285,12 @@ table {
|
||||
}
|
||||
|
||||
.create-torrent .torrent-attribute label {
|
||||
width: 60px;
|
||||
margin-right: 10px;
|
||||
width: 100px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.create-torrent .torrent-attribute>div {
|
||||
width: calc(100% - 90px);
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
|
||||
.create-torrent .torrent-attribute div {
|
||||
@@ -298,18 +299,6 @@ table {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.create-torrent .torrent-attribute textarea {
|
||||
width: calc(100% - 80px);
|
||||
height: 80px;
|
||||
color: #eee;
|
||||
background-color: transparent;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
border-radius: 2px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* BUTTONS
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user