Merge branch 'next-minor' into develop
This commit is contained in:
commit
dcbdb69e22
15 changed files with 249 additions and 4 deletions
|
|
@ -19,6 +19,7 @@
|
||||||
* Add compatibility with macOS to `script/configure_bundler` [#7830](https://github.com/diaspora/diaspora/pull/7830)
|
* Add compatibility with macOS to `script/configure_bundler` [#7830](https://github.com/diaspora/diaspora/pull/7830)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
* Add `web+diaspora://` link handler [#7826](https://github.com/diaspora/diaspora/pull/7826)
|
||||||
|
|
||||||
# 0.7.6.0
|
# 0.7.6.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,4 @@
|
||||||
//= require jquery.are-you-sure
|
//= require jquery.are-you-sure
|
||||||
//= require cropperjs/dist/cropper.js
|
//= require cropperjs/dist/cropper.js
|
||||||
//= require pica
|
//= require pica
|
||||||
|
//= require protocol-handler
|
||||||
|
|
|
||||||
17
app/assets/javascripts/protocol-handler.js
Normal file
17
app/assets/javascripts/protocol-handler.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||||
|
|
||||||
|
function registerDiasporaLinksProtocol() {
|
||||||
|
var protocol = location.protocol;
|
||||||
|
var slashes = protocol.concat("//");
|
||||||
|
var host = slashes.concat(window.location.hostname);
|
||||||
|
|
||||||
|
if (location.port) {
|
||||||
|
host = host.concat(":" + location.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.navigator.registerProtocolHandler("web+diaspora", host.concat("/link?q=%s"), document.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (window.navigator.registerProtocolHandler) === "function") {
|
||||||
|
registerDiasporaLinksProtocol();
|
||||||
|
}
|
||||||
16
app/controllers/links_controller.rb
Normal file
16
app/controllers/links_controller.rb
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class LinksController < ApplicationController
|
||||||
|
def resolve
|
||||||
|
entity = DiasporaLinkService.new(query).find_or_fetch_entity
|
||||||
|
raise ActiveRecord::RecordNotFound if entity.nil?
|
||||||
|
|
||||||
|
redirect_to url_for(entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def query
|
||||||
|
@query ||= params.fetch(:q)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -22,8 +22,7 @@ class Reference < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def add_reference(author, type, guid)
|
def add_reference(author, type, guid)
|
||||||
class_name = DiasporaFederation::Entity.entity_class(type).to_s.rpartition("::").last
|
entity = Diaspora::EntityFinder.new(type, guid).find
|
||||||
entity = Diaspora::Federation::Mappings.model_class_for(class_name).find_by(guid: guid)
|
|
||||||
references.find_or_create_by(target: entity) if entity&.diaspora_handle == author
|
references.find_or_create_by(target: entity) if entity&.diaspora_handle == author
|
||||||
rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
|
rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
|
||||||
logger.warn "ignoring invalid diaspora-url: diaspora://#{author}/#{type}/#{guid}: #{e.class}: #{e.message}"
|
logger.warn "ignoring invalid diaspora-url: diaspora://#{author}/#{type}/#{guid}: #{e.class}: #{e.message}"
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ class StatusMessage < Post
|
||||||
owned_or_visible_by_user(person.owner).joins(:mentions).where(mentions: {person_id: person.id})
|
owned_or_visible_by_user(person.owner).joins(:mentions).where(mentions: {person_id: person.id})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def self.model_name
|
||||||
|
Post.model_name
|
||||||
|
end
|
||||||
|
|
||||||
def self.guids_for_author(person)
|
def self.guids_for_author(person)
|
||||||
Post.connection.select_values(Post.where(:author_id => person.id).select('posts.guid').to_sql)
|
Post.connection.select_values(Post.where(:author_id => person.id).select('posts.guid').to_sql)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
45
app/services/diaspora_link_service.rb
Normal file
45
app/services/diaspora_link_service.rb
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Encapsulates logic of processing diaspora:// links
|
||||||
|
class DiasporaLinkService
|
||||||
|
attr_reader :type, :author, :guid
|
||||||
|
|
||||||
|
def initialize(link)
|
||||||
|
@link = link.dup
|
||||||
|
parse
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_fetch_entity
|
||||||
|
entity_finder.find || fetch_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_accessor :link
|
||||||
|
|
||||||
|
def fetch_entity
|
||||||
|
DiasporaFederation::Federation::Fetcher.fetch_public(author, type, guid)
|
||||||
|
entity_finder.find
|
||||||
|
rescue DiasporaFederation::Federation::Fetcher::NotFetchable
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def entity_finder
|
||||||
|
@entity_finder ||= Diaspora::EntityFinder.new(type, guid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize
|
||||||
|
link.gsub!(%r{^web\+diaspora://}, "diaspora://") ||
|
||||||
|
link.gsub!(%r{^//}, "diaspora://") ||
|
||||||
|
%r{^diaspora://}.match(link) ||
|
||||||
|
self.link = "diaspora://#{link}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse
|
||||||
|
normalize
|
||||||
|
match = DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX.match(link)
|
||||||
|
@author = match[1]
|
||||||
|
@type = match[2]
|
||||||
|
@guid = match[3]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.row.publisher#publisher{class: ((aspect == :profile || publisher_open?) ? "mention_popup" : "closed")}
|
.row.publisher#publisher{class: ((aspect == :profile || publisher_open?) ? "mention_popup" : "closed")}
|
||||||
.content_creation
|
.content_creation
|
||||||
= form_for(StatusMessage.new) do |status|
|
= form_for StatusMessage.new, url: status_messages_path, as: :status_message do |status|
|
||||||
= status.error_messages
|
= status.error_messages
|
||||||
%params
|
%params
|
||||||
.publisher-textarea-wrapper#publisher-textarea-wrapper
|
.publisher-textarea-wrapper#publisher-textarea-wrapper
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
-# licensed under the Affero General Public License version 3 or later. See
|
-# licensed under the Affero General Public License version 3 or later. See
|
||||||
-# the COPYRIGHT file.
|
-# the COPYRIGHT file.
|
||||||
|
|
||||||
= form_for StatusMessage.new, html: {class: "control-group", data: {ajax: false}} do |status|
|
= form_for StatusMessage.new, url: status_messages_path, as: :status_message,
|
||||||
|
html: {class: "control-group", data: {ajax: false}} do |status|
|
||||||
.form-group
|
.form-group
|
||||||
= status.hidden_field :provider_display_name, value: 'mobile'
|
= status.hidden_field :provider_display_name, value: 'mobile'
|
||||||
= status.text_area :text, placeholder: t('shared.publisher.whats_on_your_mind'), rows: 4, autofocus: "autofocus", class: "form-control"
|
= status.text_area :text, placeholder: t('shared.publisher.whats_on_your_mind'), rows: 4, autofocus: "autofocus", class: "form-control"
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,8 @@ Rails.application.routes.draw do
|
||||||
#Search
|
#Search
|
||||||
get 'search' => "search#search"
|
get 'search' => "search#search"
|
||||||
|
|
||||||
|
get "link" => "links#resolve"
|
||||||
|
|
||||||
resources :conversations, except: %i(edit update destroy) do
|
resources :conversations, except: %i(edit update destroy) do
|
||||||
resources :messages, only: %i(create)
|
resources :messages, only: %i(create)
|
||||||
delete 'visibility' => 'conversation_visibilities#destroy'
|
delete 'visibility' => 'conversation_visibilities#destroy'
|
||||||
|
|
|
||||||
15
features/desktop/diaspora_links_resolve.feature
Normal file
15
features/desktop/diaspora_links_resolve.feature
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
@javascript
|
||||||
|
Feature: (web+)diaspora:// links resolve
|
||||||
|
In order to open diaspora posts on my pod from external websites
|
||||||
|
As a user
|
||||||
|
I want external links to be resolved to local pod paths
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given following user exists:
|
||||||
|
| username | email |
|
||||||
|
| Alice | alice@alice.alice |
|
||||||
|
And "alice@alice.alice" has a public post with text "This is a post accessed by an external link"
|
||||||
|
|
||||||
|
Scenario: Resolving web+diaspora:// link
|
||||||
|
When I open an external link to the first post of "alice@alice.alice"
|
||||||
|
Then I should see "This is a post accessed by an external link"
|
||||||
7
features/step_definitions/link_steps.rb
Normal file
7
features/step_definitions/link_steps.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
When /^I open an external link to the first post of "([^"]*)"$/ do |email|
|
||||||
|
user = User.find_by(email: email)
|
||||||
|
post = user.posts.first
|
||||||
|
visit(link_path(q: "web+diaspora://#{user.diaspora_handle}/post/#{post.guid}"))
|
||||||
|
end
|
||||||
22
lib/diaspora/entity_finder.rb
Normal file
22
lib/diaspora/entity_finder.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Diaspora
|
||||||
|
class EntityFinder
|
||||||
|
def initialize(type, guid)
|
||||||
|
@type = type
|
||||||
|
@guid = guid
|
||||||
|
end
|
||||||
|
|
||||||
|
def class_name
|
||||||
|
@class_name ||= DiasporaFederation::Entity.entity_class(type).to_s.rpartition("::").last
|
||||||
|
end
|
||||||
|
|
||||||
|
def find
|
||||||
|
Diaspora::Federation::Mappings.model_class_for(class_name).find_by(guid: guid)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :type, :guid
|
||||||
|
end
|
||||||
|
end
|
||||||
71
spec/controllers/links_controller_spec.rb
Normal file
71
spec/controllers/links_controller_spec.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe LinksController, type: :controller do
|
||||||
|
describe "#resolve" do
|
||||||
|
context "with post" do
|
||||||
|
let(:post) { FactoryGirl.create(:status_message) }
|
||||||
|
let(:link_text) { "#{post.author.diaspora_handle}/post/#{post.guid}" }
|
||||||
|
subject { get :resolve, params: {q: link_query} }
|
||||||
|
|
||||||
|
shared_examples "redirects to the post" do
|
||||||
|
it "redirects to the post" do
|
||||||
|
expect(subject).to redirect_to(post_url(post))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with stripped link text" do
|
||||||
|
let(:link_query) { link_text }
|
||||||
|
include_examples "redirects to the post"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with link text starting with //" do
|
||||||
|
let(:link_query) { "//#{link_text}" }
|
||||||
|
include_examples "redirects to the post"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with link text starting with diaspora://" do
|
||||||
|
let(:link_query) { "diaspora://#{link_text}" }
|
||||||
|
include_examples "redirects to the post"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with link text starting with web+diaspora://" do
|
||||||
|
let(:link_query) { "web+diaspora://#{link_text}" }
|
||||||
|
include_examples "redirects to the post"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when post is non-fetchable" do
|
||||||
|
let(:diaspora_id) { FactoryGirl.create(:person).diaspora_handle }
|
||||||
|
let(:guid) { "1234567890abcdef" }
|
||||||
|
let(:link_query) { "web+diaspora://#{diaspora_id}/post/#{guid}" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(DiasporaFederation::Federation::Fetcher)
|
||||||
|
.to receive(:fetch_public)
|
||||||
|
.with(diaspora_id, "post", guid)
|
||||||
|
.and_raise(DiasporaFederation::Federation::Fetcher::NotFetchable)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds 404" do
|
||||||
|
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user is non-fetchable" do
|
||||||
|
let(:diaspora_id) { "unknown@pod.tld" }
|
||||||
|
let(:guid) { "1234567890abcdef" }
|
||||||
|
let(:link_query) { "web+diaspora://#{diaspora_id}/post/#{guid}" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(Person)
|
||||||
|
.to receive(:find_or_fetch_by_identifier)
|
||||||
|
.with(diaspora_id)
|
||||||
|
.and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "responds 404" do
|
||||||
|
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
44
spec/services/diaspora_link_service_spec.rb
Normal file
44
spec/services/diaspora_link_service_spec.rb
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe DiasporaLinkService do
|
||||||
|
let(:service) { described_class.new(link) }
|
||||||
|
|
||||||
|
describe "#find_or_fetch_entity" do
|
||||||
|
context "when entity is known" do
|
||||||
|
let(:post) { FactoryGirl.create(:status_message) }
|
||||||
|
let(:link) { "diaspora://#{post.author.diaspora_handle}/post/#{post.guid}" }
|
||||||
|
|
||||||
|
it "returns the entity" do
|
||||||
|
expect(service.find_or_fetch_entity).to eq(post)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when entity is unknown" do
|
||||||
|
let(:remote_person) { FactoryGirl.create(:person) }
|
||||||
|
let(:guid) { "1234567890abcdef" }
|
||||||
|
let(:link) { "diaspora://#{remote_person.diaspora_handle}/post/#{guid}" }
|
||||||
|
|
||||||
|
it "fetches entity" do
|
||||||
|
expect(DiasporaFederation::Federation::Fetcher)
|
||||||
|
.to receive(:fetch_public)
|
||||||
|
.with(remote_person.diaspora_handle, "post", guid) {
|
||||||
|
FactoryGirl.create(:status_message, author: remote_person, guid: guid)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = service.find_or_fetch_entity
|
||||||
|
expect(entity).to be_a(StatusMessage)
|
||||||
|
expect(entity.guid).to eq(guid)
|
||||||
|
expect(entity.author).to eq(remote_person)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns nil when entity is non fetchable" do
|
||||||
|
expect(DiasporaFederation::Federation::Fetcher)
|
||||||
|
.to receive(:fetch_public)
|
||||||
|
.with(remote_person.diaspora_handle, "post", guid)
|
||||||
|
.and_raise(DiasporaFederation::Federation::Fetcher::NotFetchable)
|
||||||
|
|
||||||
|
expect(service.find_or_fetch_entity).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue