Merge tag '1.9.15.4' into noscript

4chan X v1.9.15.4.

Conflicts:
	src/Posting/QR.captcha.coffee
This commit is contained in:
ccd0 2014-12-12 16:36:20 -08:00
commit 77f3639772
27 changed files with 781 additions and 250 deletions

View File

@ -2,7 +2,58 @@ The attributions below are for work that has been incorporated into the script a
The links to individual versions below are to copies of the script with the update URL removed. If you want automatic updates, install the script from the links on the [main page](https://github.com/ccd0/4chan-x).
<!-- v1.9.15.x -->
### v1.9.15.4
*2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.4/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Make `Catalog Links`, `Relative Post Dates`, and `Pin Watched Threads` off by default.
### v1.9.15.3
*2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.3/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Fix sounds sometimes playing for a short time before `Disable Autoplaying Sounds` halts them (as in 1.9.14.4).
### v1.9.15.2
*2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.2/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Add `Pin Watched Threads` setting to `Index Navigation` header submenu (default: checked), moving watched threads to the beginning of 4chan X's index/catalog.
- `Alt+Click` in the 4chan X catalog watches/unwatches a thread.
- Add a `Watch Thread`/`Unwatch Thread` item to the menu in the 4chan X catalog.
### v1.9.15.1
*2014-12-07* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.1/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Center the controls added to formerly autoplaying audio by `Disable Autoplaying Sounds` (as in v1.9.14.3).
### v1.9.15.0
*2014-12-07* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.15.0/builds/4chan-X-noupdate.crx "Chromium version")]
Based on v1.9.14.2.
**MayhemYDG, Matěj Grabovský**
- Quoting now also includes the spoiler and code tags.
**ccd0**
- Quote the original URL of links rather than the title, and don't quote `(embed)`.
- Bug fixes.
<!-- v1.9.14.x -->
### v1.9.14.4
*2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.4/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Fix sounds sometimes playing for a short time before `Disable Autoplaying Sounds` halts them.
### v1.9.14.3
*2014-12-07* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.3/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Center the controls added to formerly autoplaying audio by `Disable Autoplaying Sounds`.
### v1.9.14.2
*2014-12-04* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.2/builds/4chan-X-noupdate.crx "Chromium version")]

View File

@ -25,6 +25,10 @@ module.exports = (grunt) ->
output = if parts2.length is 0 then '""' else parts2.join ' + '
"(innerHTML: #{output})"
assert = (statement, objs...) ->
return '' unless grunt.config('pkg').tests_enabled
"throw new Error 'Assertion failed: ' + `#{JSON.stringify statement}` unless #{statement}"
# Project configuration.
grunt.initConfig
pkg: grunt.file.readJSON 'package.json'
@ -35,6 +39,7 @@ module.exports = (grunt) ->
pkg = grunt.config 'pkg'
pkg.importHTML = importHTML
pkg.html = html
pkg.assert = assert
pkg.tests_enabled or= false
pkg
enumerable: true

View File

@ -1,5 +1,5 @@
/*
* 4chan X - Version 1.9.14.2
* 4chan X - Version 1.9.15.4
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE

View File

@ -39,6 +39,9 @@ If you want to install the current beta version but get updates from the stable
## Security note
4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install [HTTPS Everywhere](https://www.eff.org/https-everywhere).
## Uninstalling
4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the `[Settings]` link in the top right corner and uncheck "`Disable the native extension`" in the panel that appears.
## More information
- [Source Code](https://github.com/ccd0/4chan-x)
- [Changelog](https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md)

Binary file not shown.

View File

@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X beta
// @version 1.9.14.2
// @version 1.9.15.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
// @version 1.9.14.2
// @version 1.9.15.4
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.9.14.2' />
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.9.15.4' />
</app>
</gupdate>

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.9.14.2' />
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.9.15.4' />
</app>
</gupdate>

View File

@ -47,6 +47,8 @@ Only the latest stable version of 4chan X is available.</p>
<p>If you want to install the current beta version but get updates from the stable channel after that, install it from <a href="https://github.com/ccd0/4chan-x/raw/beta/builds/4chan-X.user.js">here</a> for Firefox or <a href="https://github.com/ccd0/4chan-x/raw/beta/builds/4chan-X.crx">here</a> for Chromium.</p>
<h2 id="security-note">Security note</h2>
<p>4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install <a href="https://www.eff.org/https-everywhere">HTTPS Everywhere</a>.</p>
<h2 id="uninstalling">Uninstalling</h2>
<p>4chan X disables the native extension, so if you uninstall 4chan X, you&#39;ll need to re-enable it. To do this, click the <code>[Settings]</code> link in the top right corner and uncheck &quot;<code>Disable the native extension</code>&quot; in the panel that appears.</p>
<h2 id="more-information">More information</h2>
<ul>
<li><a href="https://github.com/ccd0/4chan-x">Source Code</a></li>

View File

@ -3,7 +3,7 @@
"description": "Cross-browser userscript for maximum lurking on 4chan.",
"meta": {
"name": "4chan X",
"version": "1.9.14.2",
"version": "1.9.15.4",
"repo": "https://github.com/ccd0/4chan-x/",
"page": "https://github.com/ccd0/4chan-x",
"downloads": "https://ccd0.github.io/4chan-x/builds/",

View File

@ -14,7 +14,7 @@ Config =
'Link to 4chan X\'s catalog instead of the native 4chan one.'
]
'Catalog Links': [
true
false
'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'
]
'QR Shortcut': [
@ -46,7 +46,7 @@ Config =
'Localize and format timestamps.'
]
'Relative Post Dates': [
true
false
'Display dates like "3 minutes ago". Tooltip shows the timestamp.'
]
'Comment Expansion': [
@ -589,6 +589,7 @@ http://iqdb.org/?url=%TURL
'Index Sort': 'bump'
'Index Size': 'small'
'Show Replies': true
'Pin Watched Threads': false
'Anchor Hidden Threads': true
'Refreshed Navigation': false

View File

@ -24,25 +24,27 @@ Index =
Header.addShortcut @button, 1
repliesEntry = el: UI.checkbox 'Show Replies', ' Show replies'
pinEntry = el: UI.checkbox 'Pin Watched Threads', ' Pin watched threads'
anchorEntry = el: UI.checkbox 'Anchor Hidden Threads', ' Anchor hidden threads'
refNavEntry = el: UI.checkbox 'Refreshed Navigation', ' Refreshed navigation'
anchorEntry.el.title = 'Move hidden threads at the end of the index.'
pinEntry.el.title = 'Move watched threads to the start of the index.'
anchorEntry.el.title = 'Move hidden threads to the end of the index.'
refNavEntry.el.title = 'Refresh index when navigating through pages.'
for label in [repliesEntry, anchorEntry, refNavEntry]
for label in [repliesEntry, pinEntry, anchorEntry, refNavEntry]
input = label.el.firstChild
{name} = input
$.on input, 'change', $.cb.checked
switch name
when 'Show Replies'
$.on input, 'change', @cb.replies
when 'Anchor Hidden Threads'
when 'Pin Watched Threads', 'Anchor Hidden Threads'
$.on input, 'change', @cb.sort
Header.menu.addEntry
el: $.el 'span',
textContent: 'Index Navigation'
order: 98
subEntries: [repliesEntry, anchorEntry, refNavEntry]
subEntries: [repliesEntry, pinEntry, anchorEntry, refNavEntry]
$.addClass doc, 'index-loading', "#{Conf['Index Mode'].replace /\ /g, '-'}-mode"
@root = $.el 'div', className: 'board'
@ -252,9 +254,9 @@ Index =
1
else
+window.location.pathname.split('/')[2] or 1
userPageNav: (page) ->
userPageNav: (page, noRefresh) ->
state = Index.pushState {page}
if Conf['Refreshed Navigation']
if Conf['Refreshed Navigation'] and !noRefresh
Index.update state
else
Index.pageLoad state if state.page
@ -542,7 +544,7 @@ Index =
# Sticky threads
Index.sortOnTop (thread) -> thread.isSticky
# Highlighted threads
Index.sortOnTop (thread) -> thread.isOnTop
Index.sortOnTop (thread) -> thread.isOnTop or Conf['Pin Watched Threads'] and ThreadWatcher.isWatched thread
# Non-hidden threads
Index.sortOnTop((thread) -> !thread.isHidden) if Conf['Anchor Hidden Threads']
@ -561,6 +563,11 @@ Index =
nodes = Index.buildCatalogViews()
Index.sizeCatalogViews nodes
else
if Index.followedThreadID?
i = 0
i++ while Index.followedThreadID isnt Get.threadFromRoot(Index.sortedNodes[i]).ID
page = i // Index.threadsNumPerPage + 1
Index.pushState {page} if page isnt Index.getCurrentPage()
nodes = Index.buildSinglePage Index.getCurrentPage()
$.rmAll Index.root
$.rmAll Header.hover
@ -569,6 +576,8 @@ Index =
else
Index.buildReplies nodes if Conf['Show Replies']
Index.buildStructure nodes
if Index.followedThreadID? and (post = g.posts["#{g.BOARD}.#{Index.followedThreadID}"])
Header.scrollTo post.nodes.root
buildSinglePage: (pageNum) ->
nodesPerPage = Index.threadsNumPerPage

View File

@ -103,7 +103,9 @@ hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {
/* party hats */
pointer-events: none;
}
.center > audio {
/* Anti-autoplay */
audio.controls-added {
display: block;
margin: auto;
}
@ -888,6 +890,7 @@ span.hide-announcement {
-webkit-align-self: stretch;
align-self: stretch;
}
.catalog-thread.watched .werkTyme-filename,
.filter-highlight .werkTyme-filename {
border: 2px solid rgba(255, 0, 0, .5);
}
@ -909,6 +912,7 @@ span.hide-announcement {
.filter-highlight > .reply {
box-shadow: -5px 0 rgba(255, 0, 0, .5);
}
.catalog-thread.watched .catalog-thumb,
.filter-highlight .catalog-thumb {
border: 2px solid rgba(255, 0, 0, .5);
}
@ -918,6 +922,12 @@ span.hide-announcement {
:root.reveal-spoilers s > a {
color: white !important;
}
:root.reveal-spoilers .removed-spoiler::before {
content: "[spoiler]";
}
:root.reveal-spoilers .removed-spoiler::after {
content: "[/spoiler]";
}
/* Thread & Reply Hiding */
.hide-thread-button,

View File

@ -10,20 +10,20 @@
</div>
<form>
<div class=persona>
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1 tabindex=10>
<input name=email data-name=email list="list-email" placeholder=Options class=field size=1 tabindex=20>
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1 tabindex=30>
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1>
<input name=email data-name=email list="list-email" placeholder=Options class=field size=1>
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1>
</div>
<div class=textarea>
<textarea data-name=com placeholder=Comment class=field tabindex=40></textarea>
<textarea data-name=com placeholder=Comment class=field></textarea>
<span id=char-count></span>
</div>
<div id=dump-list-container>
<div id=dump-list></div>
<a id=add-post href=javascript:; title="Add a post" tabindex=50>+</a>
<a id=add-post href=javascript:; title="Add a post">+</a>
</div>
<div id=file-n-submit>
<span id=qr-filename-container class=field tabindex=60>
<span id=qr-filename-container class=field tabindex=0>
<span id=qr-no-file>No selected file</span>
<input id="qr-filename" data-name="filename" spellcheck="false">
<span id=qr-extras-container>
@ -33,9 +33,9 @@
</span>
</span>
<label id=qr-spoiler-label>
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70>
<input type=checkbox id=qr-file-spoiler title='Spoiler image'>
</label>
<input type=submit tabindex=80>
<input type=submit>
</div>
<input type=file multiple>
</form>

View File

@ -52,16 +52,20 @@ Linkify =
endNode = saved
{data} = saved
word += data
{length} = data
if end = space.exec data
# Set our snapshot and regex to start on this node at this position when the loop resumes
word += data[...end.index]
test.lastIndex = length = end.index
i--
break
else
{length} = data
word += data
links.push Linkify.makeRange node, endNode, index, length if Linkify.regString.exec word
if Linkify.regString.test word
links.push Linkify.makeRange node, endNode, index, length
<%= assert('word is links[links.length-1].toString()') %>
break unless test.lastIndex and node is endNode
@ -84,9 +88,9 @@ Linkify =
[a-z\d%/]
)
| # This should account for virtually all links posted without http:
[-a-z\d]+[.](
([-a-z\d]+[.])+(
aero|asia|biz|cat|com|coop|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2}
)([:/]|(?!.))
)([:/]|(?![^\s'"]))
| # IPv4 Addresses
[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}
| # E-mails
@ -103,10 +107,9 @@ Linkify =
text = range.toString()
# Clean start of range
i = 0
i++ while /[(\[{<>]/.test text.charAt i
i = text.search Linkify.regString
if i
if i > 0
text = text.slice i
i-- while range.startOffset + i >= range.startContainer.data.length
@ -237,9 +240,11 @@ Linkify =
"#{status}'d"
}"
link.dataset.original = link.textContent
link.textContent = text
for post2 in post.clones
for link2 in $$ 'a.linkify', post2.nodes.comment when link2.href is link.href
link2.dataset.original = link2.textContent
link2.textContent = text
return

View File

@ -1,10 +1,13 @@
AntiAutoplay =
init: ->
return if !Conf['Disable Autoplaying Sounds']
$.ready @ready
@stop audio for audio in $$ 'audio[autoplay]', doc
window.addEventListener 'loadstart', ((e) => @stop e.target), true
ready: ->
for audio in $$ 'audio[autoplay]'
audio.pause()
audio.autoplay = false
audio.controls = true
stop: (audio) ->
return unless audio.autoplay
audio.pause()
audio.autoplay = false
return if audio.controls
audio.controls = true
$.addClass audio, 'controls-added'

View File

@ -1,14 +1,10 @@
RemoveSpoilers =
init: ->
if Conf['Reveal Spoilers'] and !Conf['Remove Spoilers']
if Conf['Reveal Spoilers']
$.addClass doc, 'reveal-spoilers'
return unless Conf['Remove Spoilers']
if Conf['Reveal Spoilers']
@wrapper = (text) ->
"[spoiler]#{text}[/spoiler]"
Post.callbacks.push
name: 'Reveal Spoilers'
cb: @node
@ -16,11 +12,10 @@ RemoveSpoilers =
name: 'Reveal Spoilers'
cb: @node
wrapper: (text) ->
text
node: (post) ->
spoilers = $$ 's', @nodes.comment
for spoiler in spoilers
$.replace spoiler, $.tn RemoveSpoilers.wrapper spoiler.textContent
span = $.el 'span', className: 'removed-spoiler'
$.replace spoiler, span
$.add span, [spoiler.childNodes...]
return

View File

@ -42,6 +42,29 @@ ThreadWatcher =
Thread.callbacks.push
name: 'Thread Watcher'
cb: @node
CatalogThread.callbacks.push
name: 'Thread Watcher'
cb: @catalogNode
if g.VIEW is 'index' and Conf['JSON Navigation'] and Conf['Menu'] and g.BOARD.ID isnt 'f'
Menu.menu.addEntry
el: $.el 'a', href: 'javascript:;'
order: 6
open: ({thread}) ->
return false if Conf['Index Mode'] isnt 'catalog'
@el.textContent = if ThreadWatcher.isWatched thread
'Unwatch thread'
else
'Watch thread'
$.off @el, 'click', @cb if @cb
@cb = ->
$.event 'CloseMenu'
ThreadWatcher.toggle thread
$.on @el, 'click', @cb
true
isWatched: (thread) ->
ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID}
node: ->
toggler = $.el 'img',
@ -49,6 +72,13 @@ ThreadWatcher =
$.on toggler, 'click', ThreadWatcher.cb.toggle
$.before $('input', @OP.nodes.post), toggler
catalogNode: ->
$.addClass @nodes.root, 'watched' if ThreadWatcher.isWatched @thread
$.on @nodes.thumb.parentNode, 'click', (e) =>
return unless e.button is 0 and e.altKey
ThreadWatcher.toggle @thread
e.preventDefault()
ready: ->
$.off d, '4chanXInitFinished', ThreadWatcher.ready
return unless Main.isThisPageLegit()
@ -83,7 +113,10 @@ ThreadWatcher =
ThreadWatcher.refresh()
$.event 'CloseMenu'
toggle: ->
ThreadWatcher.toggle Get.postFromNode(@).thread
{thread} = Get.postFromNode @
Index.followedThreadID = thread.ID
ThreadWatcher.toggle thread
delete Index.followedThreadID
rm: ->
[boardID, threadID] = @parentNode.dataset.fullID.split '.'
ThreadWatcher.rm boardID, +threadID
@ -240,14 +273,17 @@ ThreadWatcher =
for threadID in threads.keys
thread = threads[threadID]
toggler = $ '.watch-thread-link', thread.OP.nodes.post
watched = ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
helper = if watched then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
helper = if ThreadWatcher.isWatched thread then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
$[helper[0]] toggler, 'watched'
$[helper[0]] thread.catalogView.nodes.root, 'watched' if thread.catalogView
toggler.title = "#{helper[1]} Thread"
for refresher in ThreadWatcher.menu.refreshers
refresher()
return
if Index.nodes and Conf['Pin Watched Threads']
Index.sort()
Index.buildIndex()
toggle: (thread) ->
boardID = thread.board.ID

View File

@ -14,7 +14,6 @@ QR.captcha =
title: 'Verification'
autocomplete: 'off'
spellcheck: false
tabIndex: 45
@nodes = {container, input}
$.on input, 'blur', QR.focusout

View File

@ -213,12 +213,34 @@ QR =
e?.preventDefault()
return unless QR.postingIsEnabled
sel = d.getSelection()
post = Get.postFromNode @
text = ">>#{post}\n"
if (s = sel.toString().trim()) and post is Get.postFromNode sel.anchorNode
s = s.replace /\n/g, '\n>'
text += ">#{s}\n"
sel = d.getSelection()
post = Get.postFromNode @
text = ">>#{post}\n"
if sel.toString().trim() and post is Get.postFromNode sel.anchorNode
range = sel.getRangeAt 0
frag = range.cloneContents()
ancestor = range.commonAncestorContainer
# Quoting the insides of a spoiler/code tag.
if $.x 'ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor
$.prepend frag, $.tn '[spoiler]'
$.add frag, $.tn '[/spoiler]'
if insideCode = $.x 'ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor
$.prepend frag, $.tn '[code]'
$.add frag, $.tn '[/code]'
for node in $$ (if insideCode then 'br' else '.prettyprint br'), frag
$.replace node, $.tn '\n'
for node in $$ 'br', frag
$.replace node, $.tn '\n>' unless node is frag.lastChild
for node in $$ 's, .removed-spoiler', frag
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
for node in $$ '.prettyprint', frag
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
for node in $$ '.linkify[data-original]', frag
$.replace node, $.tn node.dataset.original
for node in $$ '.embedder', frag
$.rm node.previousSibling if node.previousSibling?.nodeValue is ' '
$.rm node
text += ">#{frag.textContent.trim()}\n"
QR.open()
if QR.selected.isLocked