Permissions button in browser action popup (#1368)
* Add key icon * Update context icon styles * Add permissions links * Show warning badge if permissions are insufficient for certain settings * Create PermissionsUtil * Use PermissionsUtil in Backend * Update SettingsController to use PermissionsUtil * Update AnkiController to use getRequiredPermissionsForAnkiFieldValue * Show the permissions buttons/links on the context page when necessary * Update MV3 compatibility
This commit is contained in:
parent
07cd006127
commit
94db6c69fa
@ -176,6 +176,7 @@
|
|||||||
"ext/bg/js/mecab.js",
|
"ext/bg/js/mecab.js",
|
||||||
"ext/bg/js/media-utility.js",
|
"ext/bg/js/media-utility.js",
|
||||||
"ext/bg/js/options.js",
|
"ext/bg/js/options.js",
|
||||||
|
"ext/bg/js/permissions-util.js",
|
||||||
"ext/bg/js/profile-conditions.js",
|
"ext/bg/js/profile-conditions.js",
|
||||||
"ext/bg/js/request-builder.js",
|
"ext/bg/js/request-builder.js",
|
||||||
"ext/bg/js/simple-dom-parser.js",
|
"ext/bg/js/simple-dom-parser.js",
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
<script src="/bg/js/mecab.js"></script>
|
<script src="/bg/js/mecab.js"></script>
|
||||||
<script src="/bg/js/media-utility.js"></script>
|
<script src="/bg/js/media-utility.js"></script>
|
||||||
<script src="/bg/js/options.js"></script>
|
<script src="/bg/js/options.js"></script>
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
<script src="/bg/js/profile-conditions.js"></script>
|
<script src="/bg/js/profile-conditions.js"></script>
|
||||||
<script src="/bg/js/request-builder.js"></script>
|
<script src="/bg/js/request-builder.js"></script>
|
||||||
<script src="/bg/js/native-simple-dom-parser.js"></script>
|
<script src="/bg/js/native-simple-dom-parser.js"></script>
|
||||||
|
@ -25,19 +25,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div class="nav-button-container">
|
<div class="nav-button-container">
|
||||||
<button class="nav-button action-select-profile" data-icon="profile" title="Change primary profile" hidden>
|
<button class="nav-button action-select-profile" title="Change primary profile" hidden>
|
||||||
|
<span class="icon" data-icon="profile"></span>
|
||||||
<span class="profile-select-container"><select class="profile-select" id="profile-select">
|
<span class="profile-select-container"><select class="profile-select" id="profile-select">
|
||||||
<optgroup label="Primary Profile" id="profile-select-option-group"></optgroup>
|
<optgroup label="Primary Profile" id="profile-select-option-group"></optgroup>
|
||||||
</select></span>
|
</select></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="nav-button action-open-settings" data-icon="cog" title="Settings" data-hotkey='["global:openSettingsPage","title","Settings ({0})"]'>
|
<a class="nav-button action-open-settings" title="Settings" data-hotkey='["global:openSettingsPage","title","Settings ({0})"]'>
|
||||||
|
<span class="icon" data-icon="cog"></span>
|
||||||
<div class="nav-button-warning-badge no-dictionaries-enabled-warning" hidden>
|
<div class="nav-button-warning-badge no-dictionaries-enabled-warning" hidden>
|
||||||
<div class="nav-button-warning-badge-outer"></div>
|
<div class="nav-button-warning-badge-outer"></div>
|
||||||
<div class="nav-button-warning-badge-inner"></div>
|
<div class="nav-button-warning-badge-inner"></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a class="nav-button action-open-search" data-icon="magnifying-glass" title="Search" data-hotkey='["global:openSearchPage","title","Search ({0})"]'></a>
|
<a class="nav-button action-open-permissions" title="Permissions" hidden>
|
||||||
<a class="nav-button action-open-info" data-icon="question-mark" title="Information" data-hotkey='["global:openInfoPage","title","Information ({0})"]'></a>
|
<span class="icon" data-icon="key"></span>
|
||||||
|
<div class="nav-button-warning-badge permissions-required-warning" hidden>
|
||||||
|
<div class="nav-button-warning-badge-outer"></div>
|
||||||
|
<div class="nav-button-warning-badge-inner"></div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="nav-button action-open-search" title="Search" data-hotkey='["global:openSearchPage","title","Search ({0})"]'>
|
||||||
|
<span class="icon" data-icon="magnifying-glass"></span>
|
||||||
|
</a>
|
||||||
|
<a class="nav-button action-open-info" title="Information" data-hotkey='["global:openInfoPage","title","Information ({0})"]'>
|
||||||
|
<span class="icon" data-icon="question-mark-circle"></span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -53,6 +66,13 @@
|
|||||||
<div class="flex-margin-left warning-badge no-dictionaries-enabled-warning" hidden><span class="icon" data-icon="exclamation-point-short"></span></div>
|
<div class="flex-margin-left warning-badge no-dictionaries-enabled-warning" hidden><span class="icon" data-icon="exclamation-point-short"></span></div>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="link-group action-open-permissions" hidden>
|
||||||
|
<span class="link-group-icon" data-icon="chevron"></span>
|
||||||
|
<span class="link-group-label">Permissions</span>
|
||||||
|
<span class="link-group-badge">
|
||||||
|
<div class="flex-margin-left warning-badge permissions-required-warning" hidden><span class="icon" data-icon="exclamation-point-short"></span></div>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
<a class="link-group action-open-search">
|
<a class="link-group action-open-search">
|
||||||
<span class="link-group-icon" data-icon="chevron"></span><span class="link-group-label">Search</span>
|
<span class="link-group-icon" data-icon="chevron"></span><span class="link-group-label">Search</span>
|
||||||
</a>
|
</a>
|
||||||
@ -69,6 +89,8 @@
|
|||||||
<script src="/mixed/js/hotkey-help-controller.js"></script>
|
<script src="/mixed/js/hotkey-help-controller.js"></script>
|
||||||
<script src="/mixed/js/hotkey-util.js"></script>
|
<script src="/mixed/js/hotkey-util.js"></script>
|
||||||
|
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
|
|
||||||
<script src="/bg/js/context-main.js"></script>
|
<script src="/bg/js/context-main.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -75,6 +75,7 @@ label {
|
|||||||
}
|
}
|
||||||
.icon[data-icon=profile] { --icon-image: url(/mixed/img/profile.svg); }
|
.icon[data-icon=profile] { --icon-image: url(/mixed/img/profile.svg); }
|
||||||
.icon[data-icon=cog] { --icon-image: url(/mixed/img/cog.svg); }
|
.icon[data-icon=cog] { --icon-image: url(/mixed/img/cog.svg); }
|
||||||
|
.icon[data-icon=key] { --icon-image: url(/mixed/img/key.svg); }
|
||||||
.icon[data-icon=magnifying-glass] { --icon-image: url(/mixed/img/magnifying-glass.svg); }
|
.icon[data-icon=magnifying-glass] { --icon-image: url(/mixed/img/magnifying-glass.svg); }
|
||||||
.icon[data-icon=exclamation-point-short] { --icon-image: url(/mixed/img/exclamation-point-short.svg); }
|
.icon[data-icon=exclamation-point-short] { --icon-image: url(/mixed/img/exclamation-point-short.svg); }
|
||||||
.icon[data-icon=question-mark-circle] { --icon-image: url(/mixed/img/question-mark-circle.svg); }
|
.icon[data-icon=question-mark-circle] { --icon-image: url(/mixed/img/question-mark-circle.svg); }
|
||||||
@ -82,7 +83,6 @@ label {
|
|||||||
|
|
||||||
/* Page-specific styles */
|
/* Page-specific styles */
|
||||||
.link-group {
|
.link-group {
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
@ -95,6 +95,9 @@ label {
|
|||||||
transition: background-color 0.125s linear 0s;
|
transition: background-color 0.125s linear 0s;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
.link-group:not([hidden]) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.link-group:hover,
|
.link-group:hover,
|
||||||
.link-group:active {
|
.link-group:active {
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -276,23 +279,6 @@ body[data-loaded=true] .toggle-group {
|
|||||||
.nav-button+.nav-button {
|
.nav-button+.nav-button {
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
}
|
}
|
||||||
.nav-button::after {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
box-sizing: content-box;
|
|
||||||
background-color: #333333;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-position: center center;
|
|
||||||
mask-mode: alpha;
|
|
||||||
mask-size: 16px 16px;
|
|
||||||
-webkit-mask-repeat: no-repeat;
|
|
||||||
-webkit-mask-position: center center;
|
|
||||||
-webkit-mask-mode: alpha;
|
|
||||||
-webkit-mask-size: 16px 16px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.nav-button:hover {
|
.nav-button:hover {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-color: #aaaaaa;
|
border-color: #aaaaaa;
|
||||||
@ -308,21 +294,13 @@ body[data-loaded=true] .toggle-group {
|
|||||||
.nav-button:focus {
|
.nav-button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.nav-button[data-icon=magnifying-glass]::after {
|
.nav-button>.icon {
|
||||||
mask-image: url(/mixed/img/magnifying-glass.svg);
|
--icon-size: 16px 16px;
|
||||||
-webkit-mask-image: url(/mixed/img/magnifying-glass.svg);
|
display: block;
|
||||||
}
|
width: 16px;
|
||||||
.nav-button[data-icon=cog]::after {
|
height: 16px;
|
||||||
mask-image: url(/mixed/img/cog.svg);
|
box-sizing: content-box;
|
||||||
-webkit-mask-image: url(/mixed/img/cog.svg);
|
background-color: #333333;
|
||||||
}
|
|
||||||
.nav-button[data-icon=question-mark]::after {
|
|
||||||
mask-image: url(/mixed/img/question-mark-circle.svg);
|
|
||||||
-webkit-mask-image: url(/mixed/img/question-mark-circle.svg);
|
|
||||||
}
|
|
||||||
.nav-button[data-icon=profile]::after {
|
|
||||||
mask-image: url(/mixed/img/profile.svg);
|
|
||||||
-webkit-mask-image: url(/mixed/img/profile.svg);
|
|
||||||
}
|
}
|
||||||
.nav-button:first-child,
|
.nav-button:first-child,
|
||||||
.nav-button:first-child[hidden]+.nav-button {
|
.nav-button:first-child[hidden]+.nav-button {
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
|
|
||||||
<script src="/mixed/js/document-focus-controller.js"></script>
|
<script src="/mixed/js/document-focus-controller.js"></script>
|
||||||
<script src="/mixed/js/html-template-collection.js"></script>
|
<script src="/mixed/js/html-template-collection.js"></script>
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
<script src="/bg/js/settings/settings-controller.js"></script>
|
<script src="/bg/js/settings/settings-controller.js"></script>
|
||||||
<script src="/bg/js/settings/backup-controller.js"></script>
|
<script src="/bg/js/settings/backup-controller.js"></script>
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
* MediaUtility
|
* MediaUtility
|
||||||
* ObjectPropertyAccessor
|
* ObjectPropertyAccessor
|
||||||
* OptionsUtil
|
* OptionsUtil
|
||||||
|
* PermissionsUtil
|
||||||
* ProfileConditions
|
* ProfileConditions
|
||||||
* RequestBuilder
|
* RequestBuilder
|
||||||
* Translator
|
* Translator
|
||||||
@ -83,6 +84,8 @@ class Backend {
|
|||||||
this._defaultBrowserActionTitle = null;
|
this._defaultBrowserActionTitle = null;
|
||||||
this._badgePrepareDelayTimer = null;
|
this._badgePrepareDelayTimer = null;
|
||||||
this._logErrorLevel = null;
|
this._logErrorLevel = null;
|
||||||
|
this._permissions = null;
|
||||||
|
this._permissionsUtil = new PermissionsUtil();
|
||||||
|
|
||||||
this._messageHandlers = new Map([
|
this._messageHandlers = new Map([
|
||||||
['requestBackendReadySignal', {async: false, contentScript: true, handler: this._onApiRequestBackendReadySignal.bind(this)}],
|
['requestBackendReadySignal', {async: false, contentScript: true, handler: this._onApiRequestBackendReadySignal.bind(this)}],
|
||||||
@ -174,12 +177,17 @@ class Backend {
|
|||||||
|
|
||||||
const onMessage = this._onMessageWrapper.bind(this);
|
const onMessage = this._onMessageWrapper.bind(this);
|
||||||
chrome.runtime.onMessage.addListener(onMessage);
|
chrome.runtime.onMessage.addListener(onMessage);
|
||||||
|
|
||||||
|
const onPermissionsChanged = this._onWebExtensionEventWrapper(this._onPermissionsChanged.bind(this));
|
||||||
|
chrome.permissions.onAdded.addListener(onPermissionsChanged);
|
||||||
|
chrome.permissions.onRemoved.addListener(onPermissionsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareInternal() {
|
async _prepareInternal() {
|
||||||
try {
|
try {
|
||||||
this._prepareInternalSync();
|
this._prepareInternalSync();
|
||||||
|
|
||||||
|
this._permissions = await this._permissionsUtil.getAllPermissions();
|
||||||
this._defaultBrowserActionTitle = await this._getBrowserIconTitle();
|
this._defaultBrowserActionTitle = await this._getBrowserIconTitle();
|
||||||
this._badgePrepareDelayTimer = setTimeout(() => {
|
this._badgePrepareDelayTimer = setTimeout(() => {
|
||||||
this._badgePrepareDelayTimer = null;
|
this._badgePrepareDelayTimer = null;
|
||||||
@ -357,6 +365,10 @@ class Backend {
|
|||||||
this._sendMessageTabIgnoreResponse(tabId, {action: 'zoomChanged', params: {oldZoomFactor, newZoomFactor}});
|
this._sendMessageTabIgnoreResponse(tabId, {action: 'zoomChanged', params: {oldZoomFactor, newZoomFactor}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onPermissionsChanged() {
|
||||||
|
this._checkPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
// Message handlers
|
// Message handlers
|
||||||
|
|
||||||
_onApiRequestBackendReadySignal(_params, sender) {
|
_onApiRequestBackendReadySignal(_params, sender) {
|
||||||
@ -682,7 +694,7 @@ class Backend {
|
|||||||
|
|
||||||
let permissionsOkay = false;
|
let permissionsOkay = false;
|
||||||
try {
|
try {
|
||||||
permissionsOkay = await this._hasPermissions({permissions: ['nativeMessaging']});
|
permissionsOkay = await this._permissionsUtil.hasPermissions({permissions: ['nativeMessaging']});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
@ -1263,6 +1275,10 @@ class Backend {
|
|||||||
text = 'off';
|
text = 'off';
|
||||||
color = '#555555';
|
color = '#555555';
|
||||||
status = 'Disabled';
|
status = 'Disabled';
|
||||||
|
} else if (!this._hasRequiredPermissionsForSettings(options)) {
|
||||||
|
text = '!';
|
||||||
|
color = '#f0ad4e';
|
||||||
|
status = 'Some settings require additional permissions';
|
||||||
} else if (!this._isAnyDictionaryEnabled(options)) {
|
} else if (!this._isAnyDictionaryEnabled(options)) {
|
||||||
text = '!';
|
text = '!';
|
||||||
color = '#f0ad4e';
|
color = '#f0ad4e';
|
||||||
@ -1941,17 +1957,6 @@ class Backend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasPermissions(permissions) {
|
|
||||||
return new Promise((resolve, reject) => chrome.permissions.contains(permissions, (result) => {
|
|
||||||
const e = chrome.runtime.lastError;
|
|
||||||
if (e) {
|
|
||||||
reject(new Error(e.message));
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_getTabById(tabId) {
|
_getTabById(tabId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
chrome.tabs.get(
|
chrome.tabs.get(
|
||||||
@ -1967,4 +1972,13 @@ class Backend {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _checkPermissions() {
|
||||||
|
this._permissions = await this._permissionsUtil.getAllPermissions();
|
||||||
|
this._updateBadge();
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasRequiredPermissionsForSettings(options) {
|
||||||
|
return this._permissions === null || this._permissionsUtil.hasRequiredPermissionsForOptions(this._permissions, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,14 @@
|
|||||||
|
|
||||||
/* global
|
/* global
|
||||||
* HotkeyHelpController
|
* HotkeyHelpController
|
||||||
|
* PermissionsUtil
|
||||||
* api
|
* api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class DisplayController {
|
class DisplayController {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._optionsFull = null;
|
this._optionsFull = null;
|
||||||
|
this._permissionsUtil = new PermissionsUtil();
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
@ -40,6 +42,7 @@ class DisplayController {
|
|||||||
|
|
||||||
const optionsPageUrl = optionsFull.global.useSettingsV2 ? '/bg/settings2.html' : manifest.options_ui.page;
|
const optionsPageUrl = optionsFull.global.useSettingsV2 ? '/bg/settings2.html' : manifest.options_ui.page;
|
||||||
this._setupButtonEvents('.action-open-settings', 'openSettingsPage', chrome.runtime.getURL(optionsPageUrl));
|
this._setupButtonEvents('.action-open-settings', 'openSettingsPage', chrome.runtime.getURL(optionsPageUrl));
|
||||||
|
this._setupButtonEvents('.action-open-permissions', null, chrome.runtime.getURL('/bg/permissions.html'));
|
||||||
|
|
||||||
const {profiles, profileCurrent} = optionsFull;
|
const {profiles, profileCurrent} = optionsFull;
|
||||||
const primaryProfile = (profileCurrent >= 0 && profileCurrent < profiles.length) ? profiles[profileCurrent] : null;
|
const primaryProfile = (profileCurrent >= 0 && profileCurrent < profiles.length) ? profiles[profileCurrent] : null;
|
||||||
@ -68,6 +71,7 @@ class DisplayController {
|
|||||||
_setupButtonEvents(selector, command, url) {
|
_setupButtonEvents(selector, command, url) {
|
||||||
const nodes = document.querySelectorAll(selector);
|
const nodes = document.querySelectorAll(selector);
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
|
if (typeof command === 'string') {
|
||||||
node.addEventListener('click', (e) => {
|
node.addEventListener('click', (e) => {
|
||||||
if (e.button !== 0) { return; }
|
if (e.button !== 0) { return; }
|
||||||
api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'});
|
api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'});
|
||||||
@ -78,6 +82,7 @@ class DisplayController {
|
|||||||
api.commandExec(command, {mode: 'newTab'});
|
api.commandExec(command, {mode: 'newTab'});
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, false);
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof url === 'string') {
|
if (typeof url === 'string') {
|
||||||
node.href = url;
|
node.href = url;
|
||||||
@ -131,6 +136,7 @@ class DisplayController {
|
|||||||
toggle.addEventListener('change', onToggleChanged, false);
|
toggle.addEventListener('change', onToggleChanged, false);
|
||||||
}
|
}
|
||||||
this._updateDictionariesEnabledWarnings(options);
|
this._updateDictionariesEnabledWarnings(options);
|
||||||
|
this._updatePermissionsWarnings(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _setupHotkeys() {
|
async _setupHotkeys() {
|
||||||
@ -201,6 +207,17 @@ class DisplayController {
|
|||||||
node.hidden = hasEnabledDictionary;
|
node.hidden = hasEnabledDictionary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _updatePermissionsWarnings(options) {
|
||||||
|
const permissions = await this._permissionsUtil.getAllPermissions();
|
||||||
|
if (this._permissionsUtil.hasRequiredPermissionsForOptions(permissions, options)) { return; }
|
||||||
|
|
||||||
|
const warnings = document.querySelectorAll('.action-open-permissions,.permissions-required-warning');
|
||||||
|
for (const node of warnings) {
|
||||||
|
console.log(node);
|
||||||
|
node.hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
126
ext/bg/js/permissions-util.js
Normal file
126
ext/bg/js/permissions-util.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Yomichan Authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PermissionsUtil {
|
||||||
|
constructor() {
|
||||||
|
this._ankiFieldMarkersRequiringClipboardPermission = new Set([
|
||||||
|
'clipboard-image',
|
||||||
|
'clipboard-text'
|
||||||
|
]);
|
||||||
|
this._ankiMarkerPattern = /\{([\w-]+)\}/g;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissions(permissions) {
|
||||||
|
return new Promise((resolve, reject) => chrome.permissions.contains(permissions, (result) => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) {
|
||||||
|
reject(new Error(e.message));
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setPermissionsGranted(permissions, shouldHave) {
|
||||||
|
return (
|
||||||
|
shouldHave ?
|
||||||
|
new Promise((resolve, reject) => chrome.permissions.request(permissions, (result) => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) {
|
||||||
|
reject(new Error(e.message));
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
})) :
|
||||||
|
new Promise((resolve, reject) => chrome.permissions.remove(permissions, (result) => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) {
|
||||||
|
reject(new Error(e.message));
|
||||||
|
} else {
|
||||||
|
resolve(!result);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllPermissions() {
|
||||||
|
return new Promise((resolve, reject) => chrome.permissions.getAll((result) => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) {
|
||||||
|
reject(new Error(e.message));
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequiredPermissionsForAnkiFieldValue(fieldValue) {
|
||||||
|
const markers = this._getAnkiFieldMarkers(fieldValue);
|
||||||
|
const markerPermissions = this._ankiFieldMarkersRequiringClipboardPermission;
|
||||||
|
for (const marker of markers) {
|
||||||
|
if (markerPermissions.has(marker)) {
|
||||||
|
return ['clipboardRead'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRequiredPermissionsForOptions(permissions, options) {
|
||||||
|
const permissionsSet = new Set(permissions.permissions);
|
||||||
|
|
||||||
|
if (!permissionsSet.has('nativeMessaging')) {
|
||||||
|
if (options.parsing.enableMecabParser) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permissionsSet.has('clipboardRead')) {
|
||||||
|
if (options.clipboard.enableBackgroundMonitor || options.clipboard.enableSearchPageMonitor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const fieldMarkersRequiringClipboardPermission = this._ankiFieldMarkersRequiringClipboardPermission;
|
||||||
|
const fieldsList = [
|
||||||
|
options.anki.terms.fields,
|
||||||
|
options.anki.kanji.fields
|
||||||
|
];
|
||||||
|
for (const fields of fieldsList) {
|
||||||
|
for (const fieldValue of Object.values(fields)) {
|
||||||
|
const markers = this._getAnkiFieldMarkers(fieldValue);
|
||||||
|
for (const marker of markers) {
|
||||||
|
if (fieldMarkersRequiringClipboardPermission.has(marker)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_getAnkiFieldMarkers(fieldValue) {
|
||||||
|
const pattern = this._ankiMarkerPattern;
|
||||||
|
const markers = [];
|
||||||
|
let match;
|
||||||
|
while ((match = pattern.exec(fieldValue)) !== null) {
|
||||||
|
markers.push(match[1]);
|
||||||
|
}
|
||||||
|
return markers;
|
||||||
|
}
|
||||||
|
}
|
@ -34,10 +34,6 @@ class AnkiController {
|
|||||||
onRemoved: this._removeCardController.bind(this),
|
onRemoved: this._removeCardController.bind(this),
|
||||||
isStale: this._isCardControllerStale.bind(this)
|
isStale: this._isCardControllerStale.bind(this)
|
||||||
});
|
});
|
||||||
this._fieldMarkersRequiringClipboardPermission = new Set([
|
|
||||||
'clipboard-image',
|
|
||||||
'clipboard-text'
|
|
||||||
]);
|
|
||||||
this._stringComparer = new Intl.Collator(); // Locale does not matter
|
this._stringComparer = new Intl.Collator(); // Locale does not matter
|
||||||
this._getAnkiDataPromise = null;
|
this._getAnkiDataPromise = null;
|
||||||
this._ankiErrorContainer = null;
|
this._ankiErrorContainer = null;
|
||||||
@ -157,13 +153,7 @@ class AnkiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRequiredPermissions(fieldValue) {
|
getRequiredPermissions(fieldValue) {
|
||||||
const markers = this._getFieldMarkers(fieldValue);
|
return this._settingsController.permissionsUtil.getRequiredPermissionsForAnkiFieldValue(fieldValue);
|
||||||
for (const marker of markers) {
|
|
||||||
if (this._fieldMarkersRequiringClipboardPermission.has(marker)) {
|
|
||||||
return ['clipboardRead'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
containsAnyMarker(field) {
|
containsAnyMarker(field) {
|
||||||
@ -338,16 +328,6 @@ class AnkiController {
|
|||||||
this._ankiErrorMessageDetailsToggle.hidden = false;
|
this._ankiErrorMessageDetailsToggle.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getFieldMarkers(fieldValue) {
|
|
||||||
const pattern = /\{([\w-]+)\}/g;
|
|
||||||
const markers = [];
|
|
||||||
let match;
|
|
||||||
while ((match = pattern.exec(fieldValue)) !== null) {
|
|
||||||
markers.push(match[1]);
|
|
||||||
}
|
|
||||||
return markers;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sortStringArray(array) {
|
_sortStringArray(array) {
|
||||||
const stringComparer = this._stringComparer;
|
const stringComparer = this._stringComparer;
|
||||||
array.sort((a, b) => stringComparer.compare(a, b));
|
array.sort((a, b) => stringComparer.compare(a, b));
|
||||||
@ -656,7 +636,7 @@ class AnkiCardController {
|
|||||||
|
|
||||||
async _requestPermissions(permissions) {
|
async _requestPermissions(permissions) {
|
||||||
try {
|
try {
|
||||||
await this._settingsController.setPermissionsGranted(permissions, true);
|
await this._settingsController.permissionsUtil.setPermissionsGranted({permissions}, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yomichan.logError(e);
|
yomichan.logError(e);
|
||||||
}
|
}
|
||||||
@ -669,8 +649,8 @@ class AnkiCardController {
|
|||||||
node.dataset.requiredPermission = permissions.join(' ');
|
node.dataset.requiredPermission = permissions.join(' ');
|
||||||
const hasPermissions = await (
|
const hasPermissions = await (
|
||||||
request ?
|
request ?
|
||||||
this._settingsController.setPermissionsGranted(permissions, true) :
|
this._settingsController.permissionsUtil.setPermissionsGranted({permissions}, true) :
|
||||||
this._settingsController.hasPermissions(permissions)
|
this._settingsController.permissionsUtil.hasPermissions({permissions})
|
||||||
);
|
);
|
||||||
node.dataset.hasPermissions = `${hasPermissions}`;
|
node.dataset.hasPermissions = `${hasPermissions}`;
|
||||||
} else {
|
} else {
|
||||||
|
@ -87,7 +87,7 @@ class BackupController {
|
|||||||
const optionsFull = await this._settingsController.getOptionsFull();
|
const optionsFull = await this._settingsController.getOptionsFull();
|
||||||
const environment = await api.getEnvironmentInfo();
|
const environment = await api.getEnvironmentInfo();
|
||||||
const fieldTemplatesDefault = await api.getDefaultAnkiFieldTemplates();
|
const fieldTemplatesDefault = await api.getDefaultAnkiFieldTemplates();
|
||||||
const permissions = await this._settingsController.getAllPermissions();
|
const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
|
||||||
|
|
||||||
// Format options
|
// Format options
|
||||||
for (const {options} of optionsFull.profiles) {
|
for (const {options} of optionsFull.profiles) {
|
||||||
|
@ -71,13 +71,13 @@ class PermissionsToggleController {
|
|||||||
|
|
||||||
if (value || !hasPermissionsSetting) {
|
if (value || !hasPermissionsSetting) {
|
||||||
toggle.checked = valuePre;
|
toggle.checked = valuePre;
|
||||||
const requiredPermissions = this._getRequiredPermissions(toggle);
|
const permissions = this._getRequiredPermissions(toggle);
|
||||||
try {
|
try {
|
||||||
value = await this._settingsController.setPermissionsGranted(requiredPermissions, value);
|
value = await this._settingsController.permissionsUtil.setPermissionsGranted({permissions}, value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
value = valuePre;
|
value = valuePre;
|
||||||
try {
|
try {
|
||||||
value = await this._settingsController.hasPermissions(requiredPermissions);
|
value = await this._settingsController.permissionsUtil.hasPermissions({permissions});
|
||||||
} catch (error2) {
|
} catch (error2) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ class PermissionsToggleController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _updateValidity() {
|
async _updateValidity() {
|
||||||
const permissions = await this._settingsController.getAllPermissions();
|
const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
|
||||||
this._onPermissionsChanged({permissions});
|
this._onPermissionsChanged({permissions});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
/* global
|
/* global
|
||||||
* HtmlTemplateCollection
|
* HtmlTemplateCollection
|
||||||
* OptionsUtil
|
* OptionsUtil
|
||||||
|
* PermissionsUtil
|
||||||
* api
|
* api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ class SettingsController extends EventDispatcher {
|
|||||||
this._pageExitPreventions = new Set();
|
this._pageExitPreventions = new Set();
|
||||||
this._pageExitPreventionEventListeners = new EventListenerCollection();
|
this._pageExitPreventionEventListeners = new EventListenerCollection();
|
||||||
this._templates = new HtmlTemplateCollection(document);
|
this._templates = new HtmlTemplateCollection(document);
|
||||||
|
this._permissionsUtil = new PermissionsUtil();
|
||||||
}
|
}
|
||||||
|
|
||||||
get source() {
|
get source() {
|
||||||
@ -44,6 +46,10 @@ class SettingsController extends EventDispatcher {
|
|||||||
this._setProfileIndex(value);
|
this._setProfileIndex(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get permissionsUtil() {
|
||||||
|
return this._permissionsUtil;
|
||||||
|
}
|
||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
yomichan.on('optionsUpdated', this._onOptionsUpdated.bind(this));
|
yomichan.on('optionsUpdated', this._onOptionsUpdated.bind(this));
|
||||||
chrome.permissions.onAdded.addListener(this._onPermissionsChanged.bind(this));
|
chrome.permissions.onAdded.addListener(this._onPermissionsChanged.bind(this));
|
||||||
@ -134,50 +140,6 @@ class SettingsController extends EventDispatcher {
|
|||||||
return optionsFull;
|
return optionsFull;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermissions(permissions) {
|
|
||||||
return new Promise((resolve, reject) => chrome.permissions.contains({permissions}, (result) => {
|
|
||||||
const e = chrome.runtime.lastError;
|
|
||||||
if (e) {
|
|
||||||
reject(new Error(e.message));
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
setPermissionsGranted(permissions, shouldHave) {
|
|
||||||
return (
|
|
||||||
shouldHave ?
|
|
||||||
new Promise((resolve, reject) => chrome.permissions.request({permissions}, (result) => {
|
|
||||||
const e = chrome.runtime.lastError;
|
|
||||||
if (e) {
|
|
||||||
reject(new Error(e.message));
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
})) :
|
|
||||||
new Promise((resolve, reject) => chrome.permissions.remove({permissions}, (result) => {
|
|
||||||
const e = chrome.runtime.lastError;
|
|
||||||
if (e) {
|
|
||||||
reject(new Error(e.message));
|
|
||||||
} else {
|
|
||||||
resolve(!result);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllPermissions() {
|
|
||||||
return new Promise((resolve, reject) => chrome.permissions.getAll((result) => {
|
|
||||||
const e = chrome.runtime.lastError;
|
|
||||||
if (e) {
|
|
||||||
reject(new Error(e.message));
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
_setProfileIndex(value) {
|
_setProfileIndex(value) {
|
||||||
@ -242,7 +204,7 @@ class SettingsController extends EventDispatcher {
|
|||||||
const event = 'permissionsChanged';
|
const event = 'permissionsChanged';
|
||||||
if (!this.hasListeners(event)) { return; }
|
if (!this.hasListeners(event)) { return; }
|
||||||
|
|
||||||
const permissions = await this.getAllPermissions();
|
const permissions = await this._permissionsUtil.getAllPermissions();
|
||||||
this.trigger(event, {permissions});
|
this.trigger(event, {permissions});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@
|
|||||||
|
|
||||||
<script src="/mixed/js/document-focus-controller.js"></script>
|
<script src="/mixed/js/document-focus-controller.js"></script>
|
||||||
<script src="/mixed/js/html-template-collection.js"></script>
|
<script src="/mixed/js/html-template-collection.js"></script>
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
<script src="/bg/js/settings/permissions-toggle-controller.js"></script>
|
<script src="/bg/js/settings/permissions-toggle-controller.js"></script>
|
||||||
<script src="/bg/js/settings/settings-controller.js"></script>
|
<script src="/bg/js/settings/settings-controller.js"></script>
|
||||||
|
|
||||||
|
@ -1305,6 +1305,7 @@
|
|||||||
<script src="/bg/js/dictionary-importer.js"></script>
|
<script src="/bg/js/dictionary-importer.js"></script>
|
||||||
<script src="/bg/js/json-schema.js"></script>
|
<script src="/bg/js/json-schema.js"></script>
|
||||||
<script src="/bg/js/media-utility.js"></script>
|
<script src="/bg/js/media-utility.js"></script>
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
<script src="/bg/js/template-patcher.js"></script>
|
<script src="/bg/js/template-patcher.js"></script>
|
||||||
<script src="/bg/js/template-renderer-proxy.js"></script>
|
<script src="/bg/js/template-renderer-proxy.js"></script>
|
||||||
|
|
||||||
|
@ -3219,6 +3219,7 @@
|
|||||||
<script src="/bg/js/dictionary-importer.js"></script>
|
<script src="/bg/js/dictionary-importer.js"></script>
|
||||||
<script src="/bg/js/json-schema.js"></script>
|
<script src="/bg/js/json-schema.js"></script>
|
||||||
<script src="/bg/js/media-utility.js"></script>
|
<script src="/bg/js/media-utility.js"></script>
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
<script src="/bg/js/template-patcher.js"></script>
|
<script src="/bg/js/template-patcher.js"></script>
|
||||||
<script src="/bg/js/template-renderer-proxy.js"></script>
|
<script src="/bg/js/template-renderer-proxy.js"></script>
|
||||||
|
|
||||||
|
@ -336,6 +336,7 @@
|
|||||||
<script src="/bg/js/dictionary-importer.js"></script>
|
<script src="/bg/js/dictionary-importer.js"></script>
|
||||||
<script src="/bg/js/json-schema.js"></script>
|
<script src="/bg/js/json-schema.js"></script>
|
||||||
<script src="/bg/js/media-utility.js"></script>
|
<script src="/bg/js/media-utility.js"></script>
|
||||||
|
<script src="/bg/js/permissions-util.js"></script>
|
||||||
|
|
||||||
<script src="/bg/js/settings/dictionary-controller.js"></script>
|
<script src="/bg/js/settings/dictionary-controller.js"></script>
|
||||||
<script src="/bg/js/settings/dictionary-import-controller.js"></script>
|
<script src="/bg/js/settings/dictionary-import-controller.js"></script>
|
||||||
|
@ -216,6 +216,7 @@
|
|||||||
.icon[data-icon=right-chevron] { --icon-image: url(/mixed/img/right-chevron.svg); }
|
.icon[data-icon=right-chevron] { --icon-image: url(/mixed/img/right-chevron.svg); }
|
||||||
.icon[data-icon=plus-thick] { --icon-image: url(/mixed/img/plus-thick.svg); }
|
.icon[data-icon=plus-thick] { --icon-image: url(/mixed/img/plus-thick.svg); }
|
||||||
.icon[data-icon=clipboard] { --icon-image: url(/mixed/img/clipboard.svg); }
|
.icon[data-icon=clipboard] { --icon-image: url(/mixed/img/clipboard.svg); }
|
||||||
|
.icon[data-icon=key] { --icon-image: url(/mixed/img/key.svg); }
|
||||||
.icon[data-icon=material-down-arrow] {
|
.icon[data-icon=material-down-arrow] {
|
||||||
--icon-image: url(/mixed/img/material-down-arrow.svg);
|
--icon-image: url(/mixed/img/material-down-arrow.svg);
|
||||||
--icon-size: var(--material-arrow-dimension2) var(--material-arrow-dimension1);
|
--icon-size: var(--material-arrow-dimension2) var(--material-arrow-dimension1);
|
||||||
|
1
ext/mixed/img/key.svg
Normal file
1
ext/mixed/img/key.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M10.662 1c-2.3128.04784-4.1626 1.9367-4.1621 4.25a4.24996 4.24996 0 00.14648 1.1035L.74988 12.25v1.25l1.25 1.5h2l.5-.5V13h1.5v-1.5h1.5V10h1.5l.64648-.64648a4.2498 4.2498 0 001.1035.14648c2.3472 0 4.25-1.9028 4.25-4.25S13.09706 1 10.74986 1a4.24994 4.24994 0 00-.08789 0zm.83789 2c.82843 0 1.5.67157 1.5 1.5s-.67157 1.5-1.5 1.5-1.5-.67157-1.5-1.5.67157-1.5 1.5-1.5zm-4.75 4.75l.75.75-4.75 4.75-.75-.75z"/></svg>
|
After Width: | Height: | Size: 520 B |
@ -36,6 +36,7 @@ self.importScripts(
|
|||||||
'/bg/js/mecab.js',
|
'/bg/js/mecab.js',
|
||||||
'/bg/js/media-utility.js',
|
'/bg/js/media-utility.js',
|
||||||
'/bg/js/options.js',
|
'/bg/js/options.js',
|
||||||
|
'/bg/js/permissions-util.js',
|
||||||
'/bg/js/profile-conditions.js',
|
'/bg/js/profile-conditions.js',
|
||||||
'/bg/js/request-builder.js',
|
'/bg/js/request-builder.js',
|
||||||
'/bg/js/simple-dom-parser.js',
|
'/bg/js/simple-dom-parser.js',
|
||||||
|
@ -25,11 +25,11 @@
|
|||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="32"
|
inkscape:zoom="22.627417"
|
||||||
inkscape:cx="7.2634756"
|
inkscape:cx="8.8042238"
|
||||||
inkscape:cy="9.0903162"
|
inkscape:cy="5.555245"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer50"
|
inkscape:current-layer="layer51"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
units="px"
|
units="px"
|
||||||
inkscape:snap-center="true"
|
inkscape:snap-center="true"
|
||||||
@ -1594,11 +1594,22 @@
|
|||||||
<g
|
<g
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer50"
|
id="layer50"
|
||||||
inkscape:label="Clipboard">
|
inkscape:label="Clipboard"
|
||||||
|
style="display:none">
|
||||||
<path
|
<path
|
||||||
id="rect1074"
|
id="rect1074"
|
||||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||||
d="M 8,0.5 C 7.5857864,0.5 7.25,0.83578644 7.25,1.25 7.25,1.5 7,1.5 6.5,1.5 c -0.554,0 -1,0.446 -1,1 V 3 h 5 V 2.5 c 0,-0.554 -0.446,-1 -1,-1 C 9,1.5 8.75,1.5 8.75,1.25 8.75,0.83578644 8.4142136,0.5 8,0.5 Z M 2.5,2 v 13 h 11 V 2 H 10.90625 C 10.962878,2.1572391 11,2.3243683 11,2.5 V 3 h 1.5 v 11 h -9 V 3 H 5 V 2.5 C 5,2.3243683 5.0371223,2.1572391 5.09375,2 Z m 2,3 v 1 h 7 V 5 Z m 0,2 v 1 h 7 V 7 Z m 0,2 v 1 h 7 V 9 Z m 0,2 v 1 h 5 v -1 z"
|
d="M 8,0.5 C 7.5857864,0.5 7.25,0.83578644 7.25,1.25 7.25,1.5 7,1.5 6.5,1.5 c -0.554,0 -1,0.446 -1,1 V 3 h 5 V 2.5 c 0,-0.554 -0.446,-1 -1,-1 C 9,1.5 8.75,1.5 8.75,1.25 8.75,0.83578644 8.4142136,0.5 8,0.5 Z M 2.5,2 v 13 h 11 V 2 H 10.90625 C 10.962878,2.1572391 11,2.3243683 11,2.5 V 3 h 1.5 v 11 h -9 V 3 H 5 V 2.5 C 5,2.3243683 5.0371223,2.1572391 5.09375,2 Z m 2,3 v 1 h 7 V 5 Z m 0,2 v 1 h 7 V 7 Z m 0,2 v 1 h 7 V 9 Z m 0,2 v 1 h 5 v -1 z"
|
||||||
sodipodi:nodetypes="ssssccsssscccccsccccccscccccccccccccccccccccc" />
|
sodipodi:nodetypes="ssssccsssscccccsccccccscccccccccccccccccccccc" />
|
||||||
</g>
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer51"
|
||||||
|
inkscape:label="Key">
|
||||||
|
<path
|
||||||
|
id="path1091"
|
||||||
|
style="fill:#000000;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
d="M 10.662109,1 C 8.349293,1.0478398 6.4995053,2.9366893 6.5,5.25 6.5002349,5.62267 6.5494854,5.9936904 6.6464844,6.3535156 L 0.75,12.25 V 13.5 L 2,15 H 4 L 4.5,14.5 V 13 H 6 V 11.5 H 7.5 V 10 H 9 L 9.6464844,9.3535156 C 10.00631,9.4505146 10.37733,9.4997651 10.75,9.5 13.09721,9.5 15,7.5972102 15,5.25 15,2.9027898 13.09721,1 10.75,1 10.7207,0.99969706 10.6914,0.99969706 10.66211,1 Z M 11.5,3 C 12.328427,3 13,3.6715729 13,4.5 13,5.3284271 12.328427,6 11.5,6 10.671573,6 10,5.3284271 10,4.5 10,3.6715729 10.671573,3 11.5,3 Z M 6.75,7.75 7.5,8.5 2.75,13.25 2,12.5 Z"
|
||||||
|
sodipodi:nodetypes="scccccccccccccccsccscssscccccc" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
Loading…
Reference in New Issue
Block a user